From b6644d4d3fb39d4915a51626f5b3914a11eefd4a Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 26 Apr 2024 18:26:41 +0300 Subject: [PATCH 001/117] initial commit for composed types implementation for typescript --- .../Refiners/TypeScriptRefiner.cs | 57 ++++++++++++++++++- src/Kiota.Builder/Writers/LanguageWriter.cs | 6 ++ .../TypeScript/CodeComposedTypeBaseWriter.cs | 31 ++++++++++ .../Writers/TypeScript/CodeConstantWriter.cs | 24 +++----- .../Writers/TypeScript/CodeFunctionWriter.cs | 24 +++++++- .../TypeScript/CodeIntersectionTypeWriter.cs | 15 +++++ .../Writers/TypeScript/CodeUnionTypeWriter.cs | 15 +++++ .../Writers/TypeScript/TypeScriptWriter.cs | 2 + 8 files changed, 155 insertions(+), 19 deletions(-) create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 6ef8b678b6..33a0593360 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -19,6 +19,12 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); DeduplicateErrorMappings(generatedCode); RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlConstructor, CodeMethodKind.RawUrlBuilder); + ConvertUnionTypesToWrapper( + generatedCode, + _configuration.UsesBackingStore, + s => s.ToFirstCharacterLowerCase(), + false + ); ReplaceReservedNames(generatedCode, new TypeScriptReservedNamesProvider(), static x => $"{x}Escaped"); ReplaceReservedExceptionPropertyNames(generatedCode, new TypeScriptExceptionsReservedNamesProvider(), static x => $"{x}Escaped"); MoveRequestBuilderPropertiesToBaseType(generatedCode, @@ -262,8 +268,56 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && if (functions.Length == 0) return null; - return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); + var children = new List(functions); + var composedTypeBase = GetOriginalComposedType(codeInterface); + children.Add(composedTypeBase is not null ? composedTypeBase : codeInterface); + return codeNamespace.TryAddCodeFile(codeInterface.Name, [.. children]); + } + + public static CodeComposedTypeBase? GetOriginalComposedType(CodeElement element) + { + if (element is CodeType codeType) + { + return GetOriginalComposedType(codeType); + } + else if (element is CodeClass codeClass) + { + return GetOriginalComposedType(codeClass); + } + else if (element is CodeInterface codeInterface) + { + return GetOriginalComposedType(codeInterface); + } + return null; } + + public static CodeComposedTypeBase? GetOriginalComposedType(CodeType codeType) + { + if (codeType?.TypeDefinition is CodeInterface ci) + { + return GetOriginalComposedType(ci); + } + else if (codeType?.TypeDefinition is CodeClass codeClass) + { + return GetOriginalComposedType(codeClass); + } + return null; + } + + public static CodeComposedTypeBase? GetOriginalComposedType(CodeInterface codeInterface) + { + if (codeInterface?.OriginalClass is CodeClass originalClass) + { + return GetOriginalComposedType(originalClass); + } + return null; + } + + public static CodeComposedTypeBase? GetOriginalComposedType(CodeClass codeClass) + { + return codeClass?.OriginalComposedType; + } + private static readonly CodeUsing[] navigationMetadataUsings = [ new CodeUsing { @@ -963,6 +1017,7 @@ private static CodeInterface CreateModelInterface(CodeClass modelClass, Func(T code) where T : CodeElement case CodeConstant codeConstant: ((ICodeElementWriter)elementWriter).WriteCodeElement(codeConstant, this); break; + case CodeUnionType codeUnionType: + ((ICodeElementWriter)elementWriter).WriteCodeElement(codeUnionType, this); + break; + case CodeIntersectionType codeIntersectionType: + ((ICodeElementWriter)elementWriter).WriteCodeElement(codeIntersectionType, this); + break; } else if (code is not CodeClass && code is not BlockDeclaration && diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs new file mode 100644 index 0000000000..42ea7acca3 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.TypeScript; + +public abstract class CodeComposedTypeBaseWriter : BaseElementWriter where TCodeComposedTypeBase : CodeComposedTypeBase where TConventionsService : TypeScriptConventionService +{ + protected CodeComposedTypeBaseWriter(TConventionsService conventionService) : base(conventionService) + { + } + public abstract string TypesDelimiter + { + get; + } + + public override void WriteCodeElement(TCodeComposedTypeBase codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + + if (!codeElement.Types.Any()) + throw new InvalidOperationException("CodeComposedTypeBase should be comprised of one or more types."); + + var codeUnionString = string.Join($" {TypesDelimiter} ", codeElement.Types.Select(x => conventions.GetTypeString(x, codeElement))); + + // TODO: documentation info + writer.WriteLine($"export type {codeElement.Name.ToFirstCharacterUpperCase()} = {codeUnionString};"); + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index c78bb77f6d..42c54426f2 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -201,22 +201,16 @@ currentType.TypeDefinition is CodeClass definitionClass && private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, bool isPrimitive, bool isEnum) { - if (isVoid) + return (isVoid, isEnum, isPrimitive || isStream, isCollection) switch { - return "sendNoResponseContent"; - } - else if (isEnum) - { - return isCollection ? "sendCollectionOfEnum" : "sendEnum"; - } - else if (isPrimitive || isStream) - { - return isCollection ? "sendCollectionOfPrimitive" : "sendPrimitive"; - } - else - { - return isCollection ? "sendCollection" : "send"; - } + (true, _, _, _) => "sendNoResponseContent", + (_, true, _, true) => "sendCollectionOfEnum", + (_, true, _, _) => "sendEnum", + (_, _, true, true) => "sendCollectionOfPrimitive", + (_, _, true, _) => "sendPrimitive", + (_, _, _, true) => "sendCollection", + _ => "send" + }; } private void WriteUriTemplateConstant(CodeConstant codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index d316d72f83..7eeb895f72 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -321,9 +321,27 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur private string? getSerializerAlias(CodeType propType, CodeFunction codeFunction, string propertySerializerName) { - if (propType.TypeDefinition?.GetImmediateParentOfType() is not CodeFile parentFile || - parentFile.FindChildByName(propertySerializerName, false) is not CodeFunction serializationFunction) - return string.Empty; + CodeFunction serializationFunction; + + if (GetOriginalComposedType(propType) is not null) + { + if (codeFunction.GetImmediateParentOfType() is not CodeFile functionParentFile || + functionParentFile.FindChildByName(propertySerializerName, false) is not CodeFunction composedTypeSerializationFunction) + { + return string.Empty; + } + serializationFunction = composedTypeSerializationFunction; + } + else + { + if (propType.TypeDefinition?.GetImmediateParentOfType() is not CodeFile parentFile || + parentFile.FindChildByName(propertySerializerName, false) is not CodeFunction foundSerializationFunction) + { + return string.Empty; + } + serializationFunction = foundSerializationFunction; + } + return conventions.GetTypeString(new CodeType { TypeDefinition = serializationFunction }, codeFunction, false); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs new file mode 100644 index 0000000000..ee5e488df0 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -0,0 +1,15 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeIntersectionTypeWriter : CodeComposedTypeBaseWriter +{ + public CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) + { + } + + public override string TypesDelimiter + { + get => " & "; + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs new file mode 100644 index 0000000000..73cf08c243 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs @@ -0,0 +1,15 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeUnionTypeWriter : CodeComposedTypeBaseWriter +{ + public CodeUnionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) + { + } + + public override string TypesDelimiter + { + get => " | "; + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 33d3eb6c45..e61cbc5d21 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -13,6 +13,8 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName) AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeFunctionWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeUnionTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeIntersectionTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); From 1aa3a62a1d27a729c9fdd786c04ecdefafd78854 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 3 May 2024 18:29:06 +0300 Subject: [PATCH 002/117] use inline declaration of composed types instead of wrapper types --- .../Refiners/TypeScriptRefiner.cs | 3 +- src/Kiota.Builder/Writers/LanguageWriter.cs | 6 ---- .../TypeScript/CodeComposedTypeBaseWriter.cs | 31 ---------------- .../Writers/TypeScript/CodeFunctionWriter.cs | 22 ++++++++++-- .../TypeScript/CodeIntersectionTypeWriter.cs | 15 -------- .../Writers/TypeScript/CodeUnionTypeWriter.cs | 15 -------- .../TypeScript/TypeScriptConventionService.cs | 36 ++++++++++++++----- .../Writers/TypeScript/TypeScriptWriter.cs | 2 -- 8 files changed, 49 insertions(+), 81 deletions(-) delete mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs delete mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs delete mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 33a0593360..e6d38f9f9d 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -270,7 +270,8 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && return null; var children = new List(functions); var composedTypeBase = GetOriginalComposedType(codeInterface); - children.Add(composedTypeBase is not null ? composedTypeBase : codeInterface); + if (composedTypeBase is null) + children.Add(codeInterface); return codeNamespace.TryAddCodeFile(codeInterface.Name, [.. children]); } diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index 3a4a8886e0..54ece0b814 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -158,12 +158,6 @@ public void Write(T code) where T : CodeElement case CodeConstant codeConstant: ((ICodeElementWriter)elementWriter).WriteCodeElement(codeConstant, this); break; - case CodeUnionType codeUnionType: - ((ICodeElementWriter)elementWriter).WriteCodeElement(codeUnionType, this); - break; - case CodeIntersectionType codeIntersectionType: - ((ICodeElementWriter)elementWriter).WriteCodeElement(codeIntersectionType, this); - break; } else if (code is not CodeClass && code is not BlockDeclaration && diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs deleted file mode 100644 index 42ea7acca3..0000000000 --- a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Linq; -using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; - -namespace Kiota.Builder.Writers.TypeScript; - -public abstract class CodeComposedTypeBaseWriter : BaseElementWriter where TCodeComposedTypeBase : CodeComposedTypeBase where TConventionsService : TypeScriptConventionService -{ - protected CodeComposedTypeBaseWriter(TConventionsService conventionService) : base(conventionService) - { - } - public abstract string TypesDelimiter - { - get; - } - - public override void WriteCodeElement(TCodeComposedTypeBase codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - - if (!codeElement.Types.Any()) - throw new InvalidOperationException("CodeComposedTypeBase should be comprised of one or more types."); - - var codeUnionString = string.Join($" {TypesDelimiter} ", codeElement.Types.Select(x => conventions.GetTypeString(x, codeElement))); - - // TODO: documentation info - writer.WriteLine($"export type {codeElement.Name.ToFirstCharacterUpperCase()} = {codeUnionString};"); - } -} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 7eeb895f72..63112faf55 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -5,6 +5,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.Refiners; using static Kiota.Builder.Refiners.TypeScriptRefiner; namespace Kiota.Builder.Writers.TypeScript; @@ -307,8 +308,7 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur if (conventions.GetTypeString(targetClassType, currentElement, false) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) { - var factoryMethod = definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName) ?? - definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName); + var factoryMethod = GetFactoryMethod(definitionClass, resultName); if (factoryMethod != null) { var methodName = conventions.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); @@ -319,6 +319,24 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); } + private static T? GetParentOfTypeOrNull(CodeInterface definitionClass) where T : class + { + try + { + return definitionClass.GetImmediateParentOfType(); + } + catch (InvalidOperationException) + { + return null; + } + } + + private static CodeFunction? GetFactoryMethod(CodeInterface definitionClass, string factoryFunctionName) + { + return GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryFunctionName) + ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryFunctionName); + } + private string? getSerializerAlias(CodeType propType, CodeFunction codeFunction, string propertySerializerName) { CodeFunction serializationFunction; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs deleted file mode 100644 index ee5e488df0..0000000000 --- a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.TypeScript; - -public class CodeIntersectionTypeWriter : CodeComposedTypeBaseWriter -{ - public CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } - - public override string TypesDelimiter - { - get => " & "; - } -} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs deleted file mode 100644 index 73cf08c243..0000000000 --- a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.TypeScript; - -public class CodeUnionTypeWriter : CodeComposedTypeBaseWriter -{ - public CodeUnionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } - - public override string TypesDelimiter - { - get => " | "; - } -} diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 9770e7ad57..00fda94295 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -5,7 +5,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; - +using Kiota.Builder.Refiners; using static Kiota.Builder.CodeDOM.CodeTypeBase; namespace Kiota.Builder.Writers.TypeScript; @@ -56,15 +56,17 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen { ArgumentNullException.ThrowIfNull(parameter); var paramType = GetTypeString(parameter.Type, targetElement); - var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind) switch + var isComposedOfPrimitives = TypeScriptRefiner.GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.Types.Any(x => IsPrimitiveType(x.Name)); + var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { - (false, CodeParameterKind.DeserializationTarget) => $" = {parameter.DefaultValue}", - (false, _) => $" = {parameter.DefaultValue} as {paramType}", - (true, _) => string.Empty, + (false, CodeParameterKind.DeserializationTarget, false) => $" = {parameter.DefaultValue}", + (false, _, false) => $" = {parameter.DefaultValue} as {paramType}", + (true, _, _) => string.Empty, + _ => string.Empty, }; - var (partialPrefix, partialSuffix) = parameter.Kind switch + var (partialPrefix, partialSuffix) = (isComposedOfPrimitives, parameter.Kind) switch { - CodeParameterKind.DeserializationTarget => ("Partial<", ">"), + (false, CodeParameterKind.DeserializationTarget) => ("Partial<", ">"), _ => (string.Empty, string.Empty), }; return $"{parameter.Name.ToFirstCharacterLowerCase()}{(parameter.Optional && parameter.Type.IsNullable ? "?" : string.Empty)}: {partialPrefix}{paramType}{partialSuffix}{(parameter.Type.IsNullable ? " | undefined" : string.Empty)}{defaultValueSuffix}"; @@ -73,9 +75,14 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen { ArgumentNullException.ThrowIfNull(code); ArgumentNullException.ThrowIfNull(targetElement); + var collectionSuffix = code.CollectionKind == CodeTypeCollectionKind.None || !includeCollectionInformation ? string.Empty : "[]"; - if (code is CodeComposedTypeBase currentUnion && currentUnion.Types.Any()) - return string.Join(" | ", currentUnion.Types.Select(x => GetTypeString(x, targetElement))) + collectionSuffix; + + if (TypeScriptRefiner.GetOriginalComposedType(code) is CodeComposedTypeBase composedType && composedType.Types.Any()) + { + var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypeString(x, targetElement))); + return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; + } if (code is CodeType currentType) { var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias : TranslateType(currentType); @@ -87,6 +94,17 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen throw new InvalidOperationException($"type of type {code.GetType()} is unknown"); } + + private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) + { + return codeComposedTypeBase switch + { + CodeUnionType _ => " | ", + CodeIntersectionType _ => " & ", + _ => throw new InvalidOperationException("unknown composed type"), + }; + } + private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { if (targetElement.GetImmediateParentOfType() is IBlock parentBlock && diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index e61cbc5d21..33d3eb6c45 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -13,8 +13,6 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName) AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeFunctionWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeUnionTypeWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeIntersectionTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); From 11a33f042cf1184054ca6d9e14b7bc6d922916ef Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 7 May 2024 20:27:42 +0300 Subject: [PATCH 003/117] serialize primitive union types --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 1 + .../Refiners/TypeScriptRefiner.cs | 118 +++++++++++++----- .../Writers/TypeScript/CodeConstantWriter.cs | 5 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 37 +++++- .../TypeScript/TypeScriptConventionService.cs | 28 ++++- 5 files changed, 150 insertions(+), 39 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 006960ec8c..0dc60df73e 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -15,6 +15,7 @@ public enum CodeMethodKind RequestGenerator, Serializer, Deserializer, + ComposedTypeDeserializer, Constructor, Getter, Setter, diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index e6d38f9f9d..507f8e0d63 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -6,6 +6,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Refiners; public class TypeScriptRefiner : CommonLanguageRefiner, ILanguageRefiner @@ -256,53 +257,112 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && private static CodeFile? GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { - var functions = codeNamespace.GetChildElements(true).OfType().Where(codeFunction => - codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Serializer && - codeFunction.OriginalLocalMethod.Parameters - .Any(x => x.Type.Name.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase)) || - - codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && - codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && - codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace) - ).ToArray(); + var functions = GetRelevantFunctions(codeInterface, codeNamespace).ToArray(); if (functions.Length == 0) return null; - var children = new List(functions); + var composedTypeBase = GetOriginalComposedType(codeInterface); if (composedTypeBase is null) - children.Add(codeInterface); + return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); + + var children = GetCodeFileElementsForComposedType(codeInterface, codeNamespace, composedTypeBase, functions); return codeNamespace.TryAddCodeFile(codeInterface.Name, [.. children]); } - public static CodeComposedTypeBase? GetOriginalComposedType(CodeElement element) + private static IEnumerable GetRelevantFunctions(CodeInterface codeInterface, CodeNamespace codeNamespace) + { + return codeNamespace.GetChildElements(true) + .OfType() + .Where(codeFunction => + IsRelevantDeserializerOrSerializer(codeFunction, codeInterface) || + IsRelevantFactory(codeFunction, codeInterface, codeNamespace)); + } + + private static bool IsRelevantDeserializerOrSerializer(CodeFunction codeFunction, CodeInterface codeInterface) + { + return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Serializer && + codeFunction.OriginalLocalMethod.Parameters.Any(x => x.Type.Name.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsRelevantFactory(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) { - if (element is CodeType codeType) + return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && + codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && + codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace); + } + + private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedTypeBase, CodeFunction[] functions) + { + var children = new List(functions); + var factoryMethods = functions.Where(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory).ToList(); + + foreach (var function in factoryMethods) { - return GetOriginalComposedType(codeType); + var method = CreateCodeMethod(codeInterface, composedTypeBase, function); + var codeFunction = new CodeFunction(method) { Name = method.Name }; + + children.Remove(function); + codeInterface.RemoveChildElement(function); + codeNamespace.RemoveChildElement(function); + children.Add(codeFunction); } - else if (element is CodeClass codeClass) + + return children; + } + + private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + { + var method = new CodeMethod { - return GetOriginalComposedType(codeClass); - } - else if (element is CodeInterface codeInterface) + Name = GetComposedTypeDeserializationMethodName(composedTypeBase), + ReturnType = composedTypeBase, + Kind = CodeMethodKind.ComposedTypeDeserializer, + Access = function.OriginalLocalMethod.Access, + IsAsync = function.OriginalLocalMethod.IsAsync, + IsStatic = function.OriginalLocalMethod.IsStatic, + Documentation = function.OriginalLocalMethod.Documentation, + Parent = codeInterface.OriginalClass, + }; + + method.AddParameter(CreateParseNodeCodeParameter()); + + return method; + } + + private static CodeParameter CreateParseNodeCodeParameter() + { + return new CodeParameter { - return GetOriginalComposedType(codeInterface); - } - return null; + Name = "node", + Type = new CodeType + { + Name = "ParseNode", + IsExternal = true, + }, + }; } - public static CodeComposedTypeBase? GetOriginalComposedType(CodeType codeType) + public static CodeComposedTypeBase? GetOriginalComposedType(CodeElement element) { - if (codeType?.TypeDefinition is CodeInterface ci) + return element switch { - return GetOriginalComposedType(ci); - } - else if (codeType?.TypeDefinition is CodeClass codeClass) + CodeType codeType => GetOriginalComposedType(codeType), + CodeClass codeClass => GetOriginalComposedType(codeClass), + CodeInterface codeInterface => GetOriginalComposedType(codeInterface), + CodeComposedTypeBase composedType => composedType, + _ => null, + }; + } + + public static CodeComposedTypeBase? GetOriginalComposedType(CodeType codeType) + { + return codeType?.TypeDefinition switch { - return GetOriginalComposedType(codeClass); - } - return null; + CodeInterface ci => GetOriginalComposedType(ci), + CodeClass cc => GetOriginalComposedType(cc), + _ => null, + }; } public static CodeComposedTypeBase? GetOriginalComposedType(CodeInterface codeInterface) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 42c54426f2..09d0711ba4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -2,6 +2,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; public class CodeConstantWriter : BaseElementWriter @@ -101,7 +102,7 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var isEnum = executorMethod.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeEnum; var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); - var isPrimitive = conventions.IsPrimitiveType(returnTypeWithoutCollectionSymbol); + var isPrimitive = IsPrimitiveType(returnTypeWithoutCollectionSymbol); writer.StartBlock($"{executorMethod.Name.ToFirstCharacterLowerCase()}: {{"); var urlTemplateValue = executorMethod.HasUrlTemplateOverride ? $"\"{executorMethod.UrlTemplateOverride}\"" : uriTemplateConstant.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"uriTemplate: {urlTemplateValue},"); @@ -167,7 +168,7 @@ private string GetTypeFactory(bool isVoid, bool isStream, CodeMethod codeElement { if (isVoid) return string.Empty; var typeName = conventions.TranslateType(codeElement.ReturnType); - if (isStream || conventions.IsPrimitiveType(typeName)) return $" \"{typeName}\""; + if (isStream || IsPrimitiveType(typeName)) return $" \"{typeName}\""; return $" {GetFactoryMethodName(codeElement.ReturnType, codeElement, writer)}"; } private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 63112faf55..3a2b0a7bff 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -5,8 +5,8 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -using Kiota.Builder.Refiners; using static Kiota.Builder.Refiners.TypeScriptRefiner; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; @@ -50,10 +50,29 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w case CodeMethodKind.ClientConstructor: WriteApiConstructorBody(parentFile, codeMethod, writer); break; + case CodeMethodKind.ComposedTypeDeserializer: + WriteComposedTypeDeserializer(codeMethod, writer); + break; default: throw new InvalidOperationException("Invalid code method kind"); } } + private void WriteComposedTypeDeserializer(CodeMethod method, LanguageWriter writer) + { + // TODO: Add implementation for object types and collections + if (GetOriginalComposedType(method.ReturnType) is CodeComposedTypeBase composedType) + { + foreach (var type in composedType.Types) + { + var nodeType = conventions.GetTypeString(type, method, false); + writer.StartBlock($"if(typeof node?.getRawValue() === \"{nodeType}\") {{"); + writer.WriteLine($"return node?.getRawValue() as {nodeType};"); + writer.CloseBlock(); + } + } + writer.WriteLine("return undefined;"); + } + private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod method, LanguageWriter writer) { WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); @@ -201,9 +220,9 @@ private string GetSerializationMethodName(CodeTypeBase propType) { if (propType.TypeDefinition is CodeEnum currentEnum) return $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>"; - else if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) + if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) return "writeByteArrayValue"; - else if (propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None) + if (propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None) { if (propType.TypeDefinition == null) return $"writeCollectionOfPrimitiveValues<{propertyType}>"; @@ -240,6 +259,10 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; if (otherProp.Kind is CodePropertyKind.BackingStore) writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); + else if (GetOriginalComposedType(otherProp.Type) is not null) + { + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetDeserializationMethodName(otherProp.Type, codeFunction)}(n); }},"); + } else { var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; @@ -279,13 +302,17 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeFunction { var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; var propertyType = conventions.GetTypeString(propType, codeFunction, false); + if (GetOriginalComposedType(propType) is CodeComposedTypeBase composedType) + { + return GetComposedTypeDeserializationMethodName(composedType); + } if (!string.IsNullOrEmpty(propertyType) && propType is CodeType currentType) { if (currentType.TypeDefinition is CodeEnum currentEnum && currentEnum.CodeEnumObject is not null) return $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})"; - else if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) + if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) return "getByteArrayValue"; - else if (isCollection) + if (isCollection) if (currentType.TypeDefinition == null) return $"getCollectionOfPrimitiveValues<{propertyType}>()"; else diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 00fda94295..af2a0b5535 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -52,6 +52,11 @@ public override string GetAccessModifier(AccessModifier access) }; } + public static bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) + { + return composedType?.Types.All(x => IsPrimitiveType(x.Name)) ?? false; + } + public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) { ArgumentNullException.ThrowIfNull(parameter); @@ -61,7 +66,6 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen { (false, CodeParameterKind.DeserializationTarget, false) => $" = {parameter.DefaultValue}", (false, _, false) => $" = {parameter.DefaultValue} as {paramType}", - (true, _, _) => string.Empty, _ => string.Empty, }; var (partialPrefix, partialSuffix) = (isComposedOfPrimitives, parameter.Kind) switch @@ -78,11 +82,14 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen var collectionSuffix = code.CollectionKind == CodeTypeCollectionKind.None || !includeCollectionInformation ? string.Empty : "[]"; - if (TypeScriptRefiner.GetOriginalComposedType(code) is CodeComposedTypeBase composedType && composedType.Types.Any()) + CodeComposedTypeBase? composedType = code is CodeComposedTypeBase originalComposedType ? originalComposedType : TypeScriptRefiner.GetOriginalComposedType(code); + + if (composedType is not null && composedType.Types.Any()) { var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypeString(x, targetElement))); return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; } + if (code is CodeType currentType) { var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias : TranslateType(currentType); @@ -140,7 +147,7 @@ private static string GetCodeTypeName(CodeType codeType) return (!string.IsNullOrEmpty(codeType.TypeDefinition?.Name) ? codeType.TypeDefinition.Name : codeType.Name).ToFirstCharacterUpperCase(); } #pragma warning disable CA1822 // Method should be static - public bool IsPrimitiveType(string typeName) + public static bool IsPrimitiveType(string typeName) { return typeName switch { @@ -200,4 +207,19 @@ private string GetDeprecationComment(IDeprecableElement element) var removalComment = element.Deprecation.RemovalDate is null ? string.Empty : $" and will be removed {element.Deprecation.RemovalDate.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}"; return $"@deprecated {element.Deprecation.GetDescription(type => GetTypeString(type, (element as CodeElement)!))}{versionComment}{dateComment}{removalComment}"; } + + public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType) + { + ArgumentNullException.ThrowIfNull(composedType); + var isCollection = composedType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; + return GetComposedTypeDeserializationMethodName(composedType, isCollection); + } + + public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType, bool isCollection) + { + ArgumentNullException.ThrowIfNull(composedType); + var propertyName = composedType.Name.ToFirstCharacterUpperCase(); + return isCollection ? $"getCollectionOf{propertyName}" : $"get{propertyName}"; + } + } From 87518d2c2ec979ce9a012f12423a62187019bbc2 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 10 May 2024 01:07:11 +0300 Subject: [PATCH 004/117] finished adding support for composed types comprised of union of primitives --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 1 + .../Refiners/TypeScriptRefiner.cs | 107 +++++++++- .../Writers/TypeScript/CodeFunctionWriter.cs | 194 ++++++++++++------ .../TypeScript/TypeScriptConventionService.cs | 8 +- 4 files changed, 234 insertions(+), 76 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 0dc60df73e..4e9ea0119f 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -16,6 +16,7 @@ public enum CodeMethodKind Serializer, Deserializer, ComposedTypeDeserializer, + ComposedTypeSerializer, Constructor, Getter, Setter, diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 507f8e0d63..5c5bb53034 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -6,12 +6,24 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; +using Kiota.Builder.Writers.TypeScript; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; +using static Kiota.Builder.Writers.TypeScript.CodeFunctionWriter; namespace Kiota.Builder.Refiners; public class TypeScriptRefiner : CommonLanguageRefiner, ILanguageRefiner { public static readonly string BackingStoreEnabledKey = "backingStoreEnabled"; + private static TypeScriptConventionService? conventionService; + + public static TypeScriptConventionService ConventionServiceInstance + { + get + { + conventionService ??= new TypeScriptConventionService(); + return conventionService; + } + } public TypeScriptRefiner(GenerationConfiguration configuration) : base(configuration) { } public override Task Refine(CodeNamespace generatedCode, CancellationToken cancellationToken) { @@ -297,27 +309,69 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac var children = new List(functions); var factoryMethods = functions.Where(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory).ToList(); - foreach (var function in factoryMethods) + // Replace the factory method + foreach (var factoryMethod in factoryMethods) { - var method = CreateCodeMethod(codeInterface, composedTypeBase, function); + var method = CreateDeserializerMethod(codeInterface, composedTypeBase, factoryMethod); var codeFunction = new CodeFunction(method) { Name = method.Name }; - children.Remove(function); - codeInterface.RemoveChildElement(function); - codeNamespace.RemoveChildElement(function); + children.Remove(factoryMethod); + codeInterface.RemoveChildElement(factoryMethod); + codeNamespace.RemoveChildElement(factoryMethod); children.Add(codeFunction); } + // Completely remove the Deserializer method if the composed type is comprised of primitive types only + if (composedTypeBase is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedTypeBase)) + { + var deserializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); + if (deserializerMethod is not null) + { + children.Remove(deserializerMethod); + codeInterface.RemoveChildElement(deserializerMethod); + codeNamespace.RemoveChildElement(deserializerMethod); + } + + // also replace the Serializer method + var serializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); + if (serializerMethod is not null) + { + var method = CreateSerializerMethod(codeInterface, composedTypeBase, serializerMethod); + var codeFunction = new CodeFunction(method) { Name = method.Name }; + + children.Remove(serializerMethod); + codeInterface.RemoveChildElement(serializerMethod); + codeNamespace.RemoveChildElement(serializerMethod); + children.Add(codeFunction); + } + } + return children; } + private static CodeMethod CreateDeserializerMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + { + var method = CreateCodeMethod(codeInterface, composedTypeBase, function); + method.ReturnType = composedTypeBase; + method.AddParameter(CreateParseNodeCodeParameter()); + return method; + } + + private static CodeMethod CreateSerializerMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + { + var method = CreateCodeMethod(codeInterface, composedTypeBase, function); + method.AddParameter(CreateKeyParameter()); + method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); + return method; + } + private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) { var method = new CodeMethod { - Name = GetComposedTypeDeserializationMethodName(composedTypeBase), - ReturnType = composedTypeBase, - Kind = CodeMethodKind.ComposedTypeDeserializer, + Name = GetSerializerOrDeserializerMethodName(composedTypeBase, function), + ReturnType = function.OriginalLocalMethod.ReturnType, + Kind = GetSerializerOrDeserializerMethodKind(function), Access = function.OriginalLocalMethod.Access, IsAsync = function.OriginalLocalMethod.IsAsync, IsStatic = function.OriginalLocalMethod.IsStatic, @@ -325,11 +379,29 @@ private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComp Parent = codeInterface.OriginalClass, }; - method.AddParameter(CreateParseNodeCodeParameter()); - return method; } + private static CodeMethodKind GetSerializerOrDeserializerMethodKind(CodeFunction function) + { + return function.OriginalLocalMethod.Kind switch + { + CodeMethodKind.Factory => CodeMethodKind.ComposedTypeDeserializer, + CodeMethodKind.Serializer => CodeMethodKind.ComposedTypeSerializer, + _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") + }; + } + + private static string GetSerializerOrDeserializerMethodName(CodeComposedTypeBase composedTypeBase, CodeFunction function) + { + return function.OriginalLocalMethod.Kind switch + { + CodeMethodKind.Factory => GetComposedTypeDeserializationMethodName(composedTypeBase), + CodeMethodKind.Serializer => GetComposedTypeSerializationMethodName(composedTypeBase), + _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") + }; + } + private static CodeParameter CreateParseNodeCodeParameter() { return new CodeParameter @@ -343,10 +415,25 @@ private static CodeParameter CreateParseNodeCodeParameter() }; } + private static CodeParameter CreateKeyParameter() + { + return new CodeParameter + { + Name = "key", + Type = new CodeType { Name = "string", IsExternal = true, IsNullable = false }, + Optional = false, + Documentation = new() + { + DescriptionTemplate = "The name of the property to write in the serialization.", + }, + }; + } + public static CodeComposedTypeBase? GetOriginalComposedType(CodeElement element) { return element switch { + CodeParameter param => GetOriginalComposedType(param.Type), CodeType codeType => GetOriginalComposedType(codeType), CodeClass codeClass => GetOriginalComposedType(codeClass), CodeInterface codeInterface => GetOriginalComposedType(codeInterface), diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 3a2b0a7bff..b679763f40 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -53,6 +53,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w case CodeMethodKind.ComposedTypeDeserializer: WriteComposedTypeDeserializer(codeMethod, writer); break; + case CodeMethodKind.ComposedTypeSerializer: + WriteComposedTypeSerializer(codeMethod, writer); + break; default: throw new InvalidOperationException("Invalid code method kind"); } } @@ -62,17 +65,46 @@ private void WriteComposedTypeDeserializer(CodeMethod method, LanguageWriter wri // TODO: Add implementation for object types and collections if (GetOriginalComposedType(method.ReturnType) is CodeComposedTypeBase composedType) { + writer.WriteLine("const nodeValue = node?.getNodeValue();"); foreach (var type in composedType.Types) { var nodeType = conventions.GetTypeString(type, method, false); - writer.StartBlock($"if(typeof node?.getRawValue() === \"{nodeType}\") {{"); - writer.WriteLine($"return node?.getRawValue() as {nodeType};"); + writer.StartBlock($"if(typeof nodeValue === \"{nodeType}\") {{"); + writer.WriteLine($"return nodeValue as {nodeType};"); writer.CloseBlock(); } } writer.WriteLine("return undefined;"); } + private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter writer) + { + var composedParam = method.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); + if (composedParam == null) return; + + + if (GetOriginalComposedType(composedParam) is CodeComposedTypeBase composedType) + { + var paramName = composedParam.Name.ToFirstCharacterLowerCase(); + writer.WriteLine($"if ({paramName} == undefined) return;"); + writer.StartBlock($"switch (typeof {paramName}) {{"); + + foreach (var type in composedType.Types) + { + var nodeType = conventions.GetTypeString(type, method, false); + var serializationName = GetSerializationMethodName(type); + if (serializationName == null || nodeType == null) continue; + + writer.StartBlock($"case \"{nodeType}\":"); + writer.WriteLine($"writer.{serializationName}(key, {paramName});"); + writer.WriteLine($"break;"); + writer.DecreaseIndent(); + } + } + + writer.CloseBlock(); + } + private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod method, LanguageWriter writer) { WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); @@ -128,18 +160,18 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) { writer.StartBlock($"case \"{mappedType.Key}\":"); - writer.WriteLine($"return {getDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); + writer.WriteLine($"return {GetDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); writer.DecreaseIndent(); } writer.CloseBlock(); writer.CloseBlock(); writer.CloseBlock(); } - var s = getDeserializationFunction(codeElement, returnType); + var s = GetDeserializationFunction(codeElement, returnType); writer.WriteLine($"return {s.ToFirstCharacterLowerCase()};"); } - private string getDeserializationFunction(CodeElement codeElement, string returnType) + private string GetDeserializationFunction(CodeElement codeElement, string returnType) { var codeNamespace = codeElement.GetImmediateParentOfType(); var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; @@ -187,9 +219,11 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro var serializationName = GetSerializationMethodName(codeProperty.Type); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; + var composedType = GetOriginalComposedType(codeProperty.Type); + if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null) { - var serializeName = getSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}"); + var serializeName = GetSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}"); writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); } else @@ -198,24 +232,40 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { writer.WriteLine($"if({modelParamName}.{codePropertyName})"); } - writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); + if (composedType is not null && conventions.IsComposedOfPrimitives(composedType)) + writer.WriteLine($"{serializationName}(writer, \"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); + else + writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); } } - private string GetSerializationMethodName(CodeTypeBase propType) + private string GetSerializationMethodName(CodeTypeBase propertyType) { - var propertyType = conventions.TranslateType(propType); - if (!string.IsNullOrEmpty(propertyType) && propType is CodeType currentType && GetSerializationMethodNameForCodeType(currentType, propertyType) is string result && !string.IsNullOrWhiteSpace(result)) - { - return result; - } - return propertyType switch + var propertyTypeName = conventions.TranslateType(propertyType); + var composedType = GetOriginalComposedType(propertyType); + var currentType = propertyType as CodeType; + + return (composedType, currentType, propertyTypeName) switch { - "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"write{propertyType.ToFirstCharacterUpperCase()}Value", - _ => $"writeObjectValue", + (CodeComposedTypeBase type, _, _) => GetComposedTypeSerializationMethodName(type), + (_, CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, + (_, _, "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration") => $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value", + _ => "writeObjectValue" }; } + public static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType) + { + ArgumentNullException.ThrowIfNull(composedType); + // handle union of primitive values + if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + { + return $"write{composedType.Name}Value"; + } + // throw unsupported exception + throw new InvalidOperationException($"Serialization for this composed type :: {composedType} :: is not supported yet"); + } + private string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) { if (propType.TypeDefinition is CodeEnum currentEnum) @@ -232,6 +282,7 @@ private string GetSerializationMethodName(CodeTypeBase propType) return null; } + private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { if (codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault() is CodeParameter param && param.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) @@ -239,35 +290,12 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter var properties = codeInterface.Properties.Where(static x => x.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.BackingStore) && !x.ExistsInBaseType); writer.StartBlock("return {"); - if (codeInterface.StartBlock.Implements.FirstOrDefault(static x => x.TypeDefinition is CodeInterface) is CodeType type && type.TypeDefinition is CodeInterface inherits) - { - writer.WriteLine($"...deserializeInto{inherits.Name.ToFirstCharacterUpperCase()}({param.Name.ToFirstCharacterLowerCase()}),"); - } - - var primaryErrorMapping = string.Empty; - var primaryErrorMappingKey = string.Empty; - var parentClass = codeFunction.OriginalMethodParentClass; - - if (parentClass.IsErrorDefinition && parentClass.AssociatedInterface is not null && parentClass.AssociatedInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterLowerCase(), static x => x.Name.ToFirstCharacterLowerCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) - { - primaryErrorMapping = $" {param.Name.ToFirstCharacterLowerCase()}.message = {param.Name.ToFirstCharacterLowerCase()}.{primaryMessageCodePath} ?? \"\";"; - primaryErrorMappingKey = primaryMessageCodePath.Split("?.", StringSplitOptions.RemoveEmptyEntries)[0]; - } + WriteInheritsBlock(codeInterface, param, writer); + var (primaryErrorMapping, primaryErrorMappingKey) = GetPrimaryErrorMapping(codeFunction, param); foreach (var otherProp in properties) { - var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; - if (otherProp.Kind is CodePropertyKind.BackingStore) - writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); - else if (GetOriginalComposedType(otherProp.Type) is not null) - { - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetDeserializationMethodName(otherProp.Type, codeFunction)}(n); }},"); - } - else - { - var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)}{defaultValueSuffix};{suffix} }},"); - } + WritePropertyBlock(otherProp, param, primaryErrorMapping, primaryErrorMappingKey, codeFunction, writer); } writer.CloseBlock(); @@ -275,6 +303,46 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter else throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); } + + private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param, LanguageWriter writer) + { + if (codeInterface.StartBlock.Implements.FirstOrDefault(static x => x.TypeDefinition is CodeInterface) is CodeType type && type.TypeDefinition is CodeInterface inherits) + { + writer.WriteLine($"...deserializeInto{inherits.Name.ToFirstCharacterUpperCase()}({param.Name.ToFirstCharacterLowerCase()}),"); + } + } + + private (string, string) GetPrimaryErrorMapping(CodeFunction codeFunction, CodeParameter param) + { + var primaryErrorMapping = string.Empty; + var primaryErrorMappingKey = string.Empty; + var parentClass = codeFunction.OriginalMethodParentClass; + + if (parentClass.IsErrorDefinition && parentClass.AssociatedInterface is not null && parentClass.AssociatedInterface.GetPrimaryMessageCodePath(static x => x.Name.ToFirstCharacterLowerCase(), static x => x.Name.ToFirstCharacterLowerCase(), "?.") is string primaryMessageCodePath && !string.IsNullOrEmpty(primaryMessageCodePath)) + { + primaryErrorMapping = $" {param.Name.ToFirstCharacterLowerCase()}.message = {param.Name.ToFirstCharacterLowerCase()}.{primaryMessageCodePath} ?? \"\";"; + primaryErrorMappingKey = primaryMessageCodePath.Split("?.", StringSplitOptions.RemoveEmptyEntries)[0]; + } + + return (primaryErrorMapping, primaryErrorMappingKey); + } + + private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, string primaryErrorMapping, string primaryErrorMappingKey, CodeFunction codeFunction, LanguageWriter writer) + { + var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; + if (otherProp.Kind is CodePropertyKind.BackingStore) + writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); + else if (GetOriginalComposedType(otherProp.Type) is not null) + { + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetDeserializationMethodName(otherProp.Type, codeFunction)}(n); }},"); + } + else + { + var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)}{defaultValueSuffix};{suffix} }},"); + } + } + private static string GetDefaultValueLiteralForProperty(CodeProperty codeProperty) { if (string.IsNullOrEmpty(codeProperty.DefaultValue)) return string.Empty; @@ -298,32 +366,34 @@ private void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter wri writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); } } - private string GetDeserializationMethodName(CodeTypeBase propType, CodeFunction codeFunction) + private string GetDeserializationMethodName(CodeTypeBase codeType, CodeFunction codeFunction) { - var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; - var propertyType = conventions.GetTypeString(propType, codeFunction, false); - if (GetOriginalComposedType(propType) is CodeComposedTypeBase composedType) + var isCollection = codeType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; + var propertyType = conventions.GetTypeString(codeType, codeFunction, false); + if (GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType) { return GetComposedTypeDeserializationMethodName(composedType); } - if (!string.IsNullOrEmpty(propertyType) && propType is CodeType currentType) + if (codeType is CodeType currentType && !string.IsNullOrEmpty(propertyType)) { - if (currentType.TypeDefinition is CodeEnum currentEnum && currentEnum.CodeEnumObject is not null) - return $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})"; - if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) - return "getByteArrayValue"; - if (isCollection) - if (currentType.TypeDefinition == null) - return $"getCollectionOfPrimitiveValues<{propertyType}>()"; - else - { - return $"getCollectionOfObjectValues<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})"; - } + return (currentType.TypeDefinition, isCollection, propertyType) switch + { + (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", + (_, _, string prop) when conventions.StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", + (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", + (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeType, codeFunction.OriginalLocalMethod)})", + _ => GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction) + }; } - return propertyType switch + return GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction); + } + + private string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeBase propType, string propertyTypeName, CodeFunction codeFunction) + { + return propertyTypeName switch { - "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyType.ToFirstCharacterUpperCase()}Value()", - _ => $"getObjectValue<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})" + "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", + _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})" }; } @@ -364,7 +434,7 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryFunctionName); } - private string? getSerializerAlias(CodeType propType, CodeFunction codeFunction, string propertySerializerName) + private string? GetSerializerAlias(CodeType propType, CodeFunction codeFunction, string propertySerializerName) { CodeFunction serializationFunction; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index af2a0b5535..86ee572605 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -52,16 +52,16 @@ public override string GetAccessModifier(AccessModifier access) }; } - public static bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) + public bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) { - return composedType?.Types.All(x => IsPrimitiveType(x.Name)) ?? false; + return composedType?.Types.All(x => IsPrimitiveType(GetTypeString(x, composedType))) ?? false; } public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) { ArgumentNullException.ThrowIfNull(parameter); var paramType = GetTypeString(parameter.Type, targetElement); - var isComposedOfPrimitives = TypeScriptRefiner.GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.Types.Any(x => IsPrimitiveType(x.Name)); + var isComposedOfPrimitives = TypeScriptRefiner.GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { (false, CodeParameterKind.DeserializationTarget, false) => $" = {parameter.DefaultValue}", @@ -211,7 +211,7 @@ private string GetDeprecationComment(IDeprecableElement element) public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType) { ArgumentNullException.ThrowIfNull(composedType); - var isCollection = composedType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; + var isCollection = composedType.CollectionKind != CodeTypeCollectionKind.None; return GetComposedTypeDeserializationMethodName(composedType, isCollection); } From 67c2872c339779a8a5ea35146438eb11afaa5bc5 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 10 May 2024 19:57:09 +0300 Subject: [PATCH 005/117] use more meaningful naming & semantics --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 1 + .../Refiners/TypeScriptRefiner.cs | 49 ++++++++++--------- .../Writers/TypeScript/CodeFunctionWriter.cs | 4 +- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 4e9ea0119f..96223616cc 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -15,6 +15,7 @@ public enum CodeMethodKind RequestGenerator, Serializer, Deserializer, + ComposedTypeFactory, ComposedTypeDeserializer, ComposedTypeSerializer, Constructor, diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 5c5bb53034..bdc5ee074c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -312,13 +312,26 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac // Replace the factory method foreach (var factoryMethod in factoryMethods) { - var method = CreateDeserializerMethod(codeInterface, composedTypeBase, factoryMethod); - var codeFunction = new CodeFunction(method) { Name = method.Name }; + var method = CreateFactoryMethodForComposedType(codeInterface, composedTypeBase, factoryMethod); + var factoryFunction = new CodeFunction(method) { Name = method.Name }; children.Remove(factoryMethod); codeInterface.RemoveChildElement(factoryMethod); codeNamespace.RemoveChildElement(factoryMethod); - children.Add(codeFunction); + children.Add(factoryFunction); + } + + // Replace the Serializer method + var serializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); + if (serializerMethod is not null) + { + var method = CreateSerializerMethodForComposedType(codeInterface, composedTypeBase, serializerMethod); + var serializerFunction = new CodeFunction(method) { Name = method.Name }; + + children.Remove(serializerMethod); + codeInterface.RemoveChildElement(serializerMethod); + codeNamespace.RemoveChildElement(serializerMethod); + children.Add(serializerFunction); } // Completely remove the Deserializer method if the composed type is comprised of primitive types only @@ -331,25 +344,12 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac codeInterface.RemoveChildElement(deserializerMethod); codeNamespace.RemoveChildElement(deserializerMethod); } - - // also replace the Serializer method - var serializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); - if (serializerMethod is not null) - { - var method = CreateSerializerMethod(codeInterface, composedTypeBase, serializerMethod); - var codeFunction = new CodeFunction(method) { Name = method.Name }; - - children.Remove(serializerMethod); - codeInterface.RemoveChildElement(serializerMethod); - codeNamespace.RemoveChildElement(serializerMethod); - children.Add(codeFunction); - } } return children; } - private static CodeMethod CreateDeserializerMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) { var method = CreateCodeMethod(codeInterface, composedTypeBase, function); method.ReturnType = composedTypeBase; @@ -357,10 +357,12 @@ private static CodeMethod CreateDeserializerMethod(CodeInterface codeInterface, return method; } - private static CodeMethod CreateSerializerMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) { - var method = CreateCodeMethod(codeInterface, composedTypeBase, function); - method.AddParameter(CreateKeyParameter()); + var method = CreateCodeMethod(codeInterface, composedType, function); + // Add the key parameter if the composed type is a union of primitive values + if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + method.AddParameter(CreateKeyParameter()); method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); return method; } @@ -371,7 +373,7 @@ private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComp { Name = GetSerializerOrDeserializerMethodName(composedTypeBase, function), ReturnType = function.OriginalLocalMethod.ReturnType, - Kind = GetSerializerOrDeserializerMethodKind(function), + Kind = GetComposedTypeMethodKind(function), Access = function.OriginalLocalMethod.Access, IsAsync = function.OriginalLocalMethod.IsAsync, IsStatic = function.OriginalLocalMethod.IsStatic, @@ -382,12 +384,13 @@ private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComp return method; } - private static CodeMethodKind GetSerializerOrDeserializerMethodKind(CodeFunction function) + private static CodeMethodKind GetComposedTypeMethodKind(CodeFunction function) { return function.OriginalLocalMethod.Kind switch { - CodeMethodKind.Factory => CodeMethodKind.ComposedTypeDeserializer, + CodeMethodKind.Factory => CodeMethodKind.ComposedTypeFactory, CodeMethodKind.Serializer => CodeMethodKind.ComposedTypeSerializer, + CodeMethodKind.Deserializer => CodeMethodKind.ComposedTypeDeserializer, _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") }; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index b679763f40..ca0a7da07f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -50,7 +50,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w case CodeMethodKind.ClientConstructor: WriteApiConstructorBody(parentFile, codeMethod, writer); break; - case CodeMethodKind.ComposedTypeDeserializer: + case CodeMethodKind.ComposedTypeFactory: WriteComposedTypeDeserializer(codeMethod, writer); break; case CodeMethodKind.ComposedTypeSerializer: @@ -69,7 +69,7 @@ private void WriteComposedTypeDeserializer(CodeMethod method, LanguageWriter wri foreach (var type in composedType.Types) { var nodeType = conventions.GetTypeString(type, method, false); - writer.StartBlock($"if(typeof nodeValue === \"{nodeType}\") {{"); + writer.StartBlock($"if (typeof nodeValue === \"{nodeType}\") {{"); writer.WriteLine($"return nodeValue as {nodeType};"); writer.CloseBlock(); } From 5225bdb843959471f0c68ad2f272968f5a83e77c Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 13 May 2024 11:04:35 +0300 Subject: [PATCH 006/117] refactor large methods --- .../Refiners/TypeScriptRefiner.cs | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index bdc5ee074c..65347ab114 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -304,49 +304,64 @@ private static bool IsRelevantFactory(CodeFunction codeFunction, CodeInterface c codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace); } - private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedTypeBase, CodeFunction[] functions) + private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, CodeFunction[] functions) { var children = new List(functions); - var factoryMethods = functions.Where(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory).ToList(); - // Replace the factory method + ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); + ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); + ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); + + return children; + } + + private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + { + var factoryMethods = children.OfType().Where(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory).ToList(); + foreach (var factoryMethod in factoryMethods) { - var method = CreateFactoryMethodForComposedType(codeInterface, composedTypeBase, factoryMethod); + var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); var factoryFunction = new CodeFunction(method) { Name = method.Name }; children.Remove(factoryMethod); - codeInterface.RemoveChildElement(factoryMethod); - codeNamespace.RemoveChildElement(factoryMethod); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, factoryMethod); children.Add(factoryFunction); } + } - // Replace the Serializer method - var serializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); + private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + { + var serializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); if (serializerMethod is not null) { - var method = CreateSerializerMethodForComposedType(codeInterface, composedTypeBase, serializerMethod); + var method = CreateSerializerMethodForComposedType(codeInterface, composedType, serializerMethod); var serializerFunction = new CodeFunction(method) { Name = method.Name }; children.Remove(serializerMethod); - codeInterface.RemoveChildElement(serializerMethod); - codeNamespace.RemoveChildElement(serializerMethod); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, serializerMethod); children.Add(serializerFunction); } + } - // Completely remove the Deserializer method if the composed type is comprised of primitive types only - if (composedTypeBase is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedTypeBase)) + private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + { + // Completely remove the deserializer method if the composed type is a union of primitive values + if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { - var deserializerMethod = Array.Find(functions, function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); + var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); if (deserializerMethod is not null) { children.Remove(deserializerMethod); - codeInterface.RemoveChildElement(deserializerMethod); - codeNamespace.RemoveChildElement(deserializerMethod); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); } } + } - return children; + private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction function) + { + codeInterface.RemoveChildElement(function); + codeNamespace.RemoveChildElement(function); } private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) From 99650f154dc79c8e3fbdb852f6f4415ff8c120d9 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 13 May 2024 19:26:05 +0300 Subject: [PATCH 007/117] unit test for TypescriptRefiner --- .../Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 18 ++++--- .../TypeScriptLanguageRefinerTests.cs | 51 +++++++++++++++++-- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 65347ab114..693d7e6500 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -7,8 +7,8 @@ using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; using Kiota.Builder.Writers.TypeScript; -using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; using static Kiota.Builder.Writers.TypeScript.CodeFunctionWriter; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Refiners; public class TypeScriptRefiner : CommonLanguageRefiner, ILanguageRefiner diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index ca0a7da07f..0513a12288 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -5,6 +5,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.SearchProviders.GitHub.GitHubClient.Repos.Item; using static Kiota.Builder.Refiners.TypeScriptRefiner; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; @@ -51,7 +52,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w WriteApiConstructorBody(parentFile, codeMethod, writer); break; case CodeMethodKind.ComposedTypeFactory: - WriteComposedTypeDeserializer(codeMethod, writer); + WriteComposedTypeFactory(codeMethod, writer); break; case CodeMethodKind.ComposedTypeSerializer: WriteComposedTypeSerializer(codeMethod, writer); @@ -60,21 +61,26 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } - private void WriteComposedTypeDeserializer(CodeMethod method, LanguageWriter writer) + private void WriteComposedTypeFactory(CodeMethod method, LanguageWriter writer) { // TODO: Add implementation for object types and collections if (GetOriginalComposedType(method.ReturnType) is CodeComposedTypeBase composedType) { writer.WriteLine("const nodeValue = node?.getNodeValue();"); + writer.StartBlock($"switch (typeof nodeValue) {{"); foreach (var type in composedType.Types) { var nodeType = conventions.GetTypeString(type, method, false); - writer.StartBlock($"if (typeof nodeValue === \"{nodeType}\") {{"); - writer.WriteLine($"return nodeValue as {nodeType};"); - writer.CloseBlock(); + writer.WriteLine($"case \"{nodeType}\":"); } + writer.IncreaseIndent(); + writer.WriteLine($"return nodeValue;"); + writer.DecreaseIndent(); + writer.StartBlock($"default:"); + writer.WriteLine($"return undefined;"); + writer.DecreaseIndent(); + writer.CloseBlock(); } - writer.WriteLine("return undefined;"); } private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter writer) diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index e7704461fa..1d11f6b969 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -1,16 +1,32 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; using Kiota.Builder.Refiners; - +using Kiota.Builder.Tests.OpenApiSampleFiles; +using Microsoft.Extensions.Logging; +using Moq; using Xunit; namespace Kiota.Builder.Tests.Refiners; -public class TypeScriptLanguageRefinerTests +public sealed class TypeScriptLanguageRefinerTests : IDisposable { + private readonly HttpClient _httpClient = new(); + + private readonly List _tempFiles = new(); + public void Dispose() + { + foreach (var file in _tempFiles) + File.Delete(file); + _httpClient.Dispose(); + GC.SuppressFinalize(this); + } + private readonly CodeNamespace root; private readonly CodeNamespace graphNS; public TypeScriptLanguageRefinerTests() @@ -683,7 +699,6 @@ public async Task ReplaceRequestConfigsQueryParams() Assert.Single(testNS.Constants.Where(static x => x.IsOfKind(CodeConstantKind.QueryParametersMapper))); } - [Fact] public async Task GeneratesCodeFiles() { @@ -823,5 +838,35 @@ public async Task AddsUsingForUntypedNode() Assert.Single(nodeUsing); Assert.Equal("@microsoft/kiota-abstractions", nodeUsing[0].Declaration.Name); } + [Fact] + public async Task ParsesAndRefinesUnionOfPrimitiveValues() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Github", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + Assert.NotNull(modelCodeFile); + var unionType = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && TypeScriptRefiner.GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null).ToList(); + Assert.True(unionType.Count > 0); + } #endregion } From 2d88aa8d2d0b45b4566aa49abcafabeed90bdc91 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 13 May 2024 19:27:24 +0300 Subject: [PATCH 008/117] ad dopen api test file --- .../OpenApiSampleFiles/GithubRepos.cs | 303 ++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs new file mode 100644 index 0000000000..e86a94a108 --- /dev/null +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs @@ -0,0 +1,303 @@ +namespace Kiota.Builder.Tests.OpenApiSampleFiles; + + +public static class GithubRepos +{ + public static readonly string OpenApiYaml = @" +openapi: 3.0.0 +info: + title: GitHub API + description: API for managing GitHub organizations and repositories + version: 1.0.0 + contact: + name: GitHub API Support + url: https://support.github.com/contact + email: support@github.com +servers: + - url: https://api.github.com +paths: + /orgs/{org}/repos: + post: + description: > + Creates a new repository in the specified organization. The authenticated user must be a member of the organization. + + **OAuth scope requirements** + + When using [OAuth](https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/), authorizations must include: + + * `public_repo` scope or `repo` scope to create a public repository. Note: For GitHub AE, use `repo` scope to create an internal repository. + * `repo` scope to create a private repository + externalDocs: + description: API method documentation + url: https://docs.github.com/rest/reference/repos#create-an-organization-repository + operationId: repos/create-in-org + parameters: + - $ref: '#/components/parameters/org' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + properties: + allow_auto_merge: + type: boolean + default: false + description: Either `true` to allow auto-merge on pull requests, or `false` to disallow auto-merge. + delete_branch_on_merge: + type: boolean + default: false + description: > + Either `true` to allow automatically deleting head branches when pull requests are merged, or `false` to prevent automatic deletion. + **The authenticated user must be an organization owner to set this property to `true`.** + description: + type: string + description: A short description of the repository. + gitignore_template: + type: string + description: > + Desired language or platform [.gitignore template](https://github.com/github/gitignore) to apply. + Use the name of the template without the extension. For example, ""Haskell"". + has_downloads: + type: boolean + default: true + description: Whether downloads are enabled. + merge_commit_message: + type: string + enum: + - PR_BODY + - PR_TITLE + - BLANK + description: > + The default value for a merge commit message. + - `PR_TITLE` - default to the pull request's title. + - `PR_BODY` - default to the pull request's body. + - `BLANK` - default to a blank commit message. + squash_merge_commit_message: + type: string + enum: + - PR_BODY + - COMMIT_MESSAGES + - BLANK + description: > + The default value for a squash merge commit message: + - `PR_BODY` - default to the pull request's body. + - `COMMIT_MESSAGES` - default to the branch's commit messages. + - `BLANK` - default to a blank commit message. + squash_merge_commit_title: + type: string + enum: + - PR_TITLE + - COMMIT_OR_PR_TITLE + description: > + The default value for a squash merge commit title: + - `PR_TITLE` - default to the pull request's title. + - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull request's title (when more than one commit). + team_id: + type: integer + description: > + The id of the team that will be granted access to this repository. This is only valid when creating a repository in an organization. + use_squash_pr_title_as_default: + type: boolean + default: false + deprecated: true + description: > + Either `true` to allow squash-merge commits to use pull request title, or `false` to use commit message. + **This property has been deprecated. Please use `squash_merge_commit_title` instead. + visibility: + type: string + enum: + - public + - private + description: The visibility of the repository. + responses: + '201': + description: Response + content: + application/json: + examples: + default: + $ref: '#/components/examples/repository' + schema: + $ref: '#/components/schemas/repository' + headers: + Location: + schema: + type: string + example: 'https://api.github.com/repos/octocat/Hello-World' + '403': + $ref: '#/components/responses/forbidden' + '422': + $ref: '#/components/responses/validation_failed' + summary: Create an organization repository + tags: + - repos + x-github: + category: repos + enabledForGitHubApps: true + githubCloudOnly: false + subcategory: null +components: + parameters: + org: + name: org + in: path + description: Organization name + required: true + schema: + type: string + responses: + forbidden: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/basic-error' + validation_failed: + description: Validation failed, or the endpoint has been spammed. + content: + application/json: + schema: + $ref: '#/components/schemas/validation-error' + schemas: + basic-error: + title: Basic Error + description: Basic Error + type: object + properties: + documentation_url: + type: string + message: + type: string + status: + type: string + url: + type: string + repository: + title: Repository + description: Repository + type: object + properties: + archived: + type: boolean + assignees_url: + type: string + branches_url: + type: string + collaborators_url: + type: string + full_name: + type: string + git_url: + type: string + labels_url: + type: string + language: + type: string + releases_url: + type: string + score: + type: integer + size: + type: integer + example: | + full_name: octocat/Hello-World + private: false + owner: + login: octocat + id: 1 + node_id: MDQ6VXNlcjE= + avatar_url: 'https://github.com/images/error/octocat_happy.gif' + gravatar_id: '' + url: 'https://api.github.com/users/octocat' + html_url: 'https://github.com/octocat' + followers_url: 'https://api.github.com/users/octocat/followers' + following_url: 'https://api.github.com/users/octocat/following{/other_user}' + gists_url: 'https://api.github.com/users/octocat/gists{/gist_id}' + starred_url: 'https://api.github.com/users/octocat/starred{/owner}{/repo}' + subscriptions_url: 'https://api.github.com/users/octocat/subscriptions' + organizations_url: 'https://api.github.com/users/octocat/orgs' + repos_url: 'https://api.github.com/users/octocat/repos' + events_url: 'https://api.github.com/users/octocat/events{/privacy}' + received_events_url: 'https://api.github.com/users/octocat/received_events' + type: User + site_admin: false + html_url: 'https://github.com/octocat/Hello-World' + description: This is your first repository + fork: false + url: 'https://api.github.com/repos/octocat/Hello-World' + created_at: '2011-01-26T19:01:12Z' + updated_at: '2011-01-26T19:14:43Z' + pushed_at: '2011-01-26T19:06:43Z' + homepage: 'https://github.com' + size: 100 + stargazers_count: 80 + watchers_count: 80 + language: JavaScript + has_issues: true + has_projects: true + has_downloads: true + has_wiki: true + has_pages: false + forks_count: 9 + mirror_url: null + archived: false + disabled: false + open_issues_count: 0 + license: + key: mit + name: MIT License + spdx_id: MIT + url: 'https://api.github.com/licenses/mit' + node_id: MDc6TGljZW5zZTEz + forks: 9 + open_issues: 0 + watchers: 80 + default_branch: master + validation-error: + title: Validation Error + description: Validation Error + type: object + required: + - message + - documentation_url + properties: + documentation_url: + type: string + errors: + type: array + items: + type: object + required: + - code + properties: + code: + type: string + field: + type: string + index: + type: integer + message: + type: string + resource: + type: string + value: + oneOf: + - type: string + nullable: true + - type: integer + nullable: true + - type: array + items: + type: string + nullable: true + message: + type: string + example: + documentation_url: 'https://developer.github.com/v3/activity/events/types/#watchevent' + message: 'You need to be signed in to watch a repository' + "; + +} From 442532f7addb9c055fd2287d29c5dbba256b922a Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 14 May 2024 11:05:58 +0300 Subject: [PATCH 009/117] unit tests for Typescript refiner --- .../TypeScriptLanguageRefinerTests.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 1d11f6b969..a01811b2f1 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -868,5 +868,94 @@ public async Task ParsesAndRefinesUnionOfPrimitiveValues() var unionType = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && TypeScriptRefiner.GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null).ToList(); Assert.True(unionType.Count > 0); } + + [Fact] + public void GetOriginalComposedType_ReturnsNull_WhenElementIsNull() + { + var codeElement = new Mock(); + var result = TypeScriptRefiner.GetOriginalComposedType(codeElement.Object); + Assert.Null(result); + } + + [Fact] + public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsComposedType() + { + var composedType = new Mock(); + var result = TypeScriptRefiner.GetOriginalComposedType(composedType.Object); + Assert.Equal(composedType.Object, result); + } + + [Fact] + public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsParameter() + { + var composedType = new Mock(); + + var codeClass = new CodeClass + { + OriginalComposedType = composedType.Object + }; + + var codeType = new CodeType() + { + TypeDefinition = codeClass, + }; + + var parameter = new CodeParameter() { Type = codeType }; + + var result = TypeScriptRefiner.GetOriginalComposedType(parameter); + Assert.Equal(composedType.Object, result); + } + + [Fact] + public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsCodeType() + { + var composedType = new Mock(); + + var codeClass = new CodeClass + { + OriginalComposedType = composedType.Object + }; + + var codeType = new CodeType() + { + TypeDefinition = codeClass, + }; + + var result = TypeScriptRefiner.GetOriginalComposedType(codeType); + Assert.Equal(composedType.Object, result); + } + + [Fact] + public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsCodeClass() + { + var composedType = new Mock(); + + var codeClass = new CodeClass + { + OriginalComposedType = composedType.Object + }; + + var result = TypeScriptRefiner.GetOriginalComposedType(codeClass); + Assert.Equal(composedType.Object, result); + } + + [Fact] + public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsCodeInterface() + { + var composedType = new Mock(); + + var codeClass = new CodeClass + { + OriginalComposedType = composedType.Object + }; + + var codeInterface = new CodeInterface() + { + OriginalClass = codeClass, + }; + + var result = TypeScriptRefiner.GetOriginalComposedType(codeInterface); + Assert.Equal(composedType.Object, result); + } #endregion } From bec78286447a19663145d10c297a5dc149eb0e38 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 15 May 2024 15:55:50 +0300 Subject: [PATCH 010/117] test coverage - cover all cases --- .../Refiners/TypeScriptLanguageRefinerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index a01811b2f1..f8d2d27ec0 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -930,7 +930,7 @@ public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsCodeClass() { var composedType = new Mock(); - var codeClass = new CodeClass + CodeElement codeClass = new CodeClass { OriginalComposedType = composedType.Object }; @@ -949,7 +949,7 @@ public void GetOriginalComposedType_ReturnsComposedType_WhenElementIsCodeInterfa OriginalComposedType = composedType.Object }; - var codeInterface = new CodeInterface() + CodeElement codeInterface = new CodeInterface() { OriginalClass = codeClass, }; From 325fcab2b91798b2ad9bb478c3b33ea027047e1e Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 16 May 2024 07:48:19 +0300 Subject: [PATCH 011/117] minor cleanup refactor for TypeScript/CodeFunctionWriter.cs --- .../Writers/TypeScript/CodeFunctionWriter.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 0513a12288..baffdb016f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -5,7 +5,6 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -using Kiota.Builder.SearchProviders.GitHub.GitHubClient.Repos.Item; using static Kiota.Builder.Refiners.TypeScriptRefiner; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; @@ -88,7 +87,6 @@ private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter write var composedParam = method.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); if (composedParam == null) return; - if (GetOriginalComposedType(composedParam) is CodeComposedTypeBase composedType) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); @@ -274,21 +272,15 @@ public static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase private string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) { - if (propType.TypeDefinition is CodeEnum currentEnum) - return $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>"; - if (conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase)) - return "writeByteArrayValue"; - if (propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None) + return propType switch { - if (propType.TypeDefinition == null) - return $"writeCollectionOfPrimitiveValues<{propertyType}>"; - else - return "writeCollectionOfObjectValues"; - } - return null; + _ when propType.TypeDefinition is CodeEnum currentEnum => $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>", + _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue", + _ when propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None => propType.TypeDefinition == null ? $"writeCollectionOfPrimitiveValues<{propertyType}>" : "writeCollectionOfObjectValues", + _ => null + }; } - private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { if (codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault() is CodeParameter param && param.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) From d2782adbd6783b253908bf6d7ad3ac172b96c371 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 16 May 2024 12:28:00 +0300 Subject: [PATCH 012/117] access the conventions service from static context --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 9 --------- .../Writers/TypeScript/TypeScriptConventionService.cs | 10 ++++++++++ .../Writers/TypeScript/TypeScriptWriter.cs | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index b335887052..2c5cde93e2 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -14,16 +14,7 @@ namespace Kiota.Builder.Refiners; public class TypeScriptRefiner : CommonLanguageRefiner, ILanguageRefiner { public static readonly string BackingStoreEnabledKey = "backingStoreEnabled"; - private static TypeScriptConventionService? conventionService; - public static TypeScriptConventionService ConventionServiceInstance - { - get - { - conventionService ??= new TypeScriptConventionService(); - return conventionService; - } - } public TypeScriptRefiner(GenerationConfiguration configuration) : base(configuration) { } public override Task Refine(CodeNamespace generatedCode, CancellationToken cancellationToken) { diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 86ee572605..0883e5c21f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -11,6 +11,16 @@ namespace Kiota.Builder.Writers.TypeScript; public class TypeScriptConventionService : CommonLanguageConventionService { + private static TypeScriptConventionService? conventionService; + public static TypeScriptConventionService ConventionServiceInstance + { + get + { + conventionService ??= new TypeScriptConventionService(); + return conventionService; + } + } + internal void WriteAutoGeneratedStart(LanguageWriter writer) { writer.WriteLine("/* tslint:disable */"); diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 33d3eb6c45..0fb1e03e27 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -1,4 +1,5 @@ using Kiota.Builder.PathSegmenters; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; @@ -7,7 +8,7 @@ public class TypeScriptWriter : LanguageWriter public TypeScriptWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new TypeScriptPathSegmenter(rootPath, clientNamespaceName); - var conventionService = new TypeScriptConventionService(); + var conventionService = ConventionServiceInstance; AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); AddOrReplaceCodeElementWriter(new CodeBlockEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); From dfe28b1a2dd8aa7ce00669e7d5eafa3d669ac9e6 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 16 May 2024 12:36:35 +0300 Subject: [PATCH 013/117] move the method to get factory name to the conventions service --- .../Writers/TypeScript/CodeConstantWriter.cs | 21 ------------------ .../TypeScript/TypeScriptConventionService.cs | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 09d0711ba4..d70d1d1147 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -178,27 +178,6 @@ private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, stri clone.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None; return conventions.GetTypeString(clone, codeElement); } - private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter writer) - { - var returnType = conventions.GetTypeString(targetClassType, currentElement, false, writer); - var targetClassName = conventions.TranslateType(targetClassType); - var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; - if (targetClassName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) - return resultName; - if (targetClassType is CodeType currentType && - currentType.TypeDefinition is CodeClass definitionClass && - definitionClass.GetImmediateParentOfType() is CodeNamespace parentNamespace && - parentNamespace.FindChildByName(resultName) is CodeFunction factoryMethod) - { - var methodName = conventions.GetTypeString(new CodeType - { - Name = resultName, - TypeDefinition = factoryMethod - }, currentElement, false, writer); - return methodName.ToFirstCharacterUpperCase();// static function is aliased - } - throw new InvalidOperationException($"Unable to find factory method for {targetClassName}"); - } private string GetSendRequestMethodName(bool isVoid, bool isStream, bool isCollection, bool isPrimitive, bool isEnum) { diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 0883e5c21f..8d70ceaa7b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -232,4 +232,26 @@ public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBa return isCollection ? $"getCollectionOf{propertyName}" : $"get{propertyName}"; } + public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter writer) + { + var returnType = ConventionServiceInstance.GetTypeString(targetClassType, currentElement, false, writer); + var targetClassName = ConventionServiceInstance.TranslateType(targetClassType); + var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; + if (targetClassName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) + return resultName; + if (targetClassType is CodeType currentType && + currentType.TypeDefinition is CodeClass definitionClass && + definitionClass.GetImmediateParentOfType() is CodeNamespace parentNamespace && + parentNamespace.FindChildByName(resultName) is CodeFunction factoryMethod) + { + var methodName = ConventionServiceInstance.GetTypeString(new CodeType + { + Name = resultName, + TypeDefinition = factoryMethod + }, currentElement, false, writer); + return methodName.ToFirstCharacterUpperCase();// static function is aliased + } + throw new InvalidOperationException($"Unable to find factory method for {targetClassName}"); + } + } From 75022fc18052afbadd2619116ae20e99d6710828 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 16 May 2024 18:15:32 +0300 Subject: [PATCH 014/117] refactor composed type to use specific writers --- .../Refiners/TypeScriptRefiner.cs | 33 +++--- src/Kiota.Builder/Writers/LanguageWriter.cs | 6 + .../TypeScript/CodeComposedTypeBaseWriter.cs | 31 +++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 79 +------------ .../TypeScript/CodeIntersectionTypeWriter.cs | 15 +++ .../Writers/TypeScript/CodeUnionTypeWriter.cs | 15 +++ .../TypeScript/TypeScriptConventionService.cs | 110 +++++++++++++----- .../Writers/TypeScript/TypeScriptWriter.cs | 2 + 8 files changed, 171 insertions(+), 120 deletions(-) create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index c3a8ed8760..312afeba79 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -265,12 +265,10 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && if (functions.Length == 0) return null; - var composedTypeBase = GetOriginalComposedType(codeInterface); - if (composedTypeBase is null) - return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); + var composedType = GetOriginalComposedType(codeInterface); + var elements = composedType is null ? new List { codeInterface }.Concat(functions) : GetCodeFileElementsForComposedType(codeInterface, codeNamespace, composedType, functions); - var children = GetCodeFileElementsForComposedType(codeInterface, codeNamespace, composedTypeBase, functions); - return codeNamespace.TryAddCodeFile(codeInterface.Name, [.. children]); + return codeNamespace.TryAddCodeFile(codeInterface.Name, elements.ToArray()); } private static IEnumerable GetRelevantFunctions(CodeInterface codeInterface, CodeNamespace codeNamespace) @@ -299,6 +297,9 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac { var children = new List(functions); + // Add the composed type, The writer will output the composed type as a type definition e.g export type Pet = Cat | Dog + children.Add(composedType); + ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); @@ -405,8 +406,8 @@ private static string GetSerializerOrDeserializerMethodName(CodeComposedTypeBase { return function.OriginalLocalMethod.Kind switch { - CodeMethodKind.Factory => GetComposedTypeDeserializationMethodName(composedTypeBase), - CodeMethodKind.Serializer => GetComposedTypeSerializationMethodName(composedTypeBase), + CodeMethodKind.Factory => GetFactoryMethodName(composedTypeBase, function), + CodeMethodKind.Serializer => GetSerializationMethodName(composedTypeBase), _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") }; } @@ -949,9 +950,9 @@ private static void AddInterfaceParamToSerializer(CodeInterface modelInterface, method.AddParameter(new CodeParameter { - Name = ReturnFinalInterfaceName(modelInterface), + Name = GetFinalInterfaceName(modelInterface), DefaultValue = "{}", - Type = new CodeType { Name = ReturnFinalInterfaceName(modelInterface), TypeDefinition = modelInterface }, + Type = new CodeType { Name = GetFinalInterfaceName(modelInterface), TypeDefinition = modelInterface }, Kind = CodeParameterKind.DeserializationTarget, }); @@ -962,7 +963,7 @@ private static void AddInterfaceParamToSerializer(CodeInterface modelInterface, Name = modelInterface.Parent.Name, Declaration = new CodeType { - Name = ReturnFinalInterfaceName(modelInterface), + Name = GetFinalInterfaceName(modelInterface), TypeDefinition = modelInterface } }); @@ -993,7 +994,7 @@ private static void RenameModelInterfacesAndRemoveClasses(CodeElement currentEle { if (currentElement is CodeInterface modelInterface && modelInterface.IsOfKind(CodeInterfaceKind.Model) && modelInterface.Parent is CodeNamespace parentNS) { - var finalName = ReturnFinalInterfaceName(modelInterface); + var finalName = GetFinalInterfaceName(modelInterface); if (!finalName.Equals(modelInterface.Name, StringComparison.Ordinal)) { if (parentNS.FindChildByName(finalName, false) is CodeClass existingClassToRemove) @@ -1013,13 +1014,13 @@ private static void RenameCodeInterfaceParamsInSerializers(CodeFunction codeFunc { if (codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault(static x => x.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface) is CodeParameter param && param.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface paramInterface) { - param.Name = ReturnFinalInterfaceName(paramInterface); + param.Name = GetFinalInterfaceName(paramInterface); } } - private static string ReturnFinalInterfaceName(CodeInterface codeInterface) + private static string GetFinalInterfaceName(CodeInterface codeInterface) { - return codeInterface.OriginalClass.Name.ToFirstCharacterUpperCase(); + return codeInterface.OriginalClass?.Name.ToFirstCharacterUpperCase() ?? throw new InvalidOperationException($"The refiner was unable to find the original class for {codeInterface.Name}"); } private static void GenerateModelInterfaces(CodeElement currentElement, Func interfaceNamingCallback) @@ -1137,7 +1138,7 @@ private static void SetTypeAsModelInterface(CodeInterface interfaceElement, Code Name = interfaceElement.Name, TypeDefinition = interfaceElement, }; - requestBuilder.RemoveUsingsByDeclarationName(ReturnFinalInterfaceName(interfaceElement)); + requestBuilder.RemoveUsingsByDeclarationName(GetFinalInterfaceName(interfaceElement)); if (!requestBuilder.Usings.Any(x => x.Declaration?.TypeDefinition == elemType.TypeDefinition)) { requestBuilder.AddUsing(new CodeUsing @@ -1206,7 +1207,7 @@ private static void ProcessModelClassDeclaration(CodeClass modelClass, CodeInter var parentInterface = CreateModelInterface(baseClass, tempInterfaceNamingCallback); var codeType = new CodeType { - Name = ReturnFinalInterfaceName(parentInterface), + Name = GetFinalInterfaceName(parentInterface), TypeDefinition = parentInterface, }; modelInterface.StartBlock.AddImplements(codeType); diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index 54ece0b814..3a4a8886e0 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -158,6 +158,12 @@ public void Write(T code) where T : CodeElement case CodeConstant codeConstant: ((ICodeElementWriter)elementWriter).WriteCodeElement(codeConstant, this); break; + case CodeUnionType codeUnionType: + ((ICodeElementWriter)elementWriter).WriteCodeElement(codeUnionType, this); + break; + case CodeIntersectionType codeIntersectionType: + ((ICodeElementWriter)elementWriter).WriteCodeElement(codeIntersectionType, this); + break; } else if (code is not CodeClass && code is not BlockDeclaration && diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs new file mode 100644 index 0000000000..6ee3a5c76b --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.TypeScript; + +public abstract class CodeComposedTypeBaseWriter : BaseElementWriter where TCodeComposedTypeBase : CodeComposedTypeBase where TConventionsService : TypeScriptConventionService +{ + protected CodeComposedTypeBaseWriter(TConventionsService conventionService) : base(conventionService) + { + } + public abstract string TypesDelimiter + { + get; + } + + public override void WriteCodeElement(TCodeComposedTypeBase codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + + if (!codeElement.Types.Any()) + throw new InvalidOperationException("CodeComposedTypeBase should be comprised of one or more types."); + + var codeUnionString = string.Join($" {TypesDelimiter} ", codeElement.Types.Select(x => conventions.GetTypeString(x, codeElement))); + + // TODO: documentation info + writer.WriteLine($"export type {codeElement.Name.ToFirstCharacterUpperCase()} = {codeUnionString};"); + } +} \ No newline at end of file diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index baffdb016f..7d188ced5f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -243,9 +243,9 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro } } - private string GetSerializationMethodName(CodeTypeBase propertyType) + public static string GetSerializationMethodName(CodeTypeBase propertyType) { - var propertyTypeName = conventions.TranslateType(propertyType); + var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); var composedType = GetOriginalComposedType(propertyType); var currentType = propertyType as CodeType; @@ -258,7 +258,7 @@ private string GetSerializationMethodName(CodeTypeBase propertyType) }; } - public static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType) + private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType) { ArgumentNullException.ThrowIfNull(composedType); // handle union of primitive values @@ -270,12 +270,12 @@ public static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase throw new InvalidOperationException($"Serialization for this composed type :: {composedType} :: is not supported yet"); } - private string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) + private static string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) { return propType switch { _ when propType.TypeDefinition is CodeEnum currentEnum => $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>", - _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue", + _ when ConventionServiceInstance.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue", _ when propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None => propType.TypeDefinition == null ? $"writeCollectionOfPrimitiveValues<{propertyType}>" : "writeCollectionOfObjectValues", _ => null }; @@ -332,7 +332,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); else if (GetOriginalComposedType(otherProp.Type) is not null) { - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetDeserializationMethodName(otherProp.Type, codeFunction)}(n); }},"); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); } else { @@ -364,73 +364,6 @@ private void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter wri writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); } } - private string GetDeserializationMethodName(CodeTypeBase codeType, CodeFunction codeFunction) - { - var isCollection = codeType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; - var propertyType = conventions.GetTypeString(codeType, codeFunction, false); - if (GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType) - { - return GetComposedTypeDeserializationMethodName(composedType); - } - if (codeType is CodeType currentType && !string.IsNullOrEmpty(propertyType)) - { - return (currentType.TypeDefinition, isCollection, propertyType) switch - { - (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", - (_, _, string prop) when conventions.StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", - (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", - (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeType, codeFunction.OriginalLocalMethod)})", - _ => GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction) - }; - } - return GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction); - } - - private string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeBase propType, string propertyTypeName, CodeFunction codeFunction) - { - return propertyTypeName switch - { - "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", - _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})" - }; - } - - private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod currentElement) - { - if (conventions.TranslateType(targetClassType) is string targetClassName) - { - var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; - if (conventions.GetTypeString(targetClassType, currentElement, false) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; - if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) - { - var factoryMethod = GetFactoryMethod(definitionClass, resultName); - if (factoryMethod != null) - { - var methodName = conventions.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); - return methodName.ToFirstCharacterUpperCase();// static function is aliased - } - } - } - throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); - } - - private static T? GetParentOfTypeOrNull(CodeInterface definitionClass) where T : class - { - try - { - return definitionClass.GetImmediateParentOfType(); - } - catch (InvalidOperationException) - { - return null; - } - } - - private static CodeFunction? GetFactoryMethod(CodeInterface definitionClass, string factoryFunctionName) - { - return GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryFunctionName) - ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryFunctionName); - } private string? GetSerializerAlias(CodeType propType, CodeFunction codeFunction, string propertySerializerName) { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs new file mode 100644 index 0000000000..ad3d1e392b --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -0,0 +1,15 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeIntersectionTypeWriter : CodeComposedTypeBaseWriter +{ + public CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) + { + } + + public override string TypesDelimiter + { + get => " & "; + } +} \ No newline at end of file diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs new file mode 100644 index 0000000000..af289f13c3 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs @@ -0,0 +1,15 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeUnionTypeWriter : CodeComposedTypeBaseWriter +{ + public CodeUnionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) + { + } + + public override string TypesDelimiter + { + get => " | "; + } +} \ No newline at end of file diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 8d70ceaa7b..9d3e44846d 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -7,6 +7,7 @@ using Kiota.Builder.Extensions; using Kiota.Builder.Refiners; using static Kiota.Builder.CodeDOM.CodeTypeBase; +using static Kiota.Builder.Refiners.TypeScriptRefiner; namespace Kiota.Builder.Writers.TypeScript; public class TypeScriptConventionService : CommonLanguageConventionService @@ -71,7 +72,7 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen { ArgumentNullException.ThrowIfNull(parameter); var paramType = GetTypeString(parameter.Type, targetElement); - var isComposedOfPrimitives = TypeScriptRefiner.GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); + var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { (false, CodeParameterKind.DeserializationTarget, false) => $" = {parameter.DefaultValue}", @@ -92,24 +93,19 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen var collectionSuffix = code.CollectionKind == CodeTypeCollectionKind.None || !includeCollectionInformation ? string.Empty : "[]"; - CodeComposedTypeBase? composedType = code is CodeComposedTypeBase originalComposedType ? originalComposedType : TypeScriptRefiner.GetOriginalComposedType(code); + CodeTypeBase codeType = GetOriginalComposedType(code) is CodeComposedTypeBase composedType ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : code; - if (composedType is not null && composedType.Types.Any()) + if (codeType is not CodeType currentType) { - var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypeString(x, targetElement))); - return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; + throw new InvalidOperationException($"type of type {code.GetType()} is unknown"); } - if (code is CodeType currentType) - { - var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias : TranslateType(currentType); - var genericParameters = currentType.GenericTypeParameterValues.Count != 0 ? - $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypeString(x, targetElement, includeCollectionInformation)))}>" : - string.Empty; - return $"{typeName}{collectionSuffix}{genericParameters}"; - } + var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias : TranslateType(currentType); + var genericParameters = currentType.GenericTypeParameterValues.Count != 0 ? + $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypeString(x, targetElement, includeCollectionInformation)))}>" : + string.Empty; - throw new InvalidOperationException($"type of type {code.GetType()} is unknown"); + return $"{typeName}{collectionSuffix}{genericParameters}"; } private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) @@ -134,6 +130,12 @@ private static string GetTypeAlias(CodeType targetType, CodeElement targetElemen return string.Empty; } + public static string TranslateType(CodeComposedTypeBase composedType) + { + ArgumentNullException.ThrowIfNull(composedType); + return composedType.Name.ToFirstCharacterUpperCase(); + } + public override string TranslateType(CodeType type) { return type?.Name switch @@ -225,33 +227,79 @@ public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBa return GetComposedTypeDeserializationMethodName(composedType, isCollection); } - public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType, bool isCollection) + private static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType, bool isCollection) { - ArgumentNullException.ThrowIfNull(composedType); var propertyName = composedType.Name.ToFirstCharacterUpperCase(); return isCollection ? $"getCollectionOf{propertyName}" : $"get{propertyName}"; } - public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter writer) + public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter? writer = null) { - var returnType = ConventionServiceInstance.GetTypeString(targetClassType, currentElement, false, writer); - var targetClassName = ConventionServiceInstance.TranslateType(targetClassType); + var composedType = GetOriginalComposedType(targetClassType); + string targetClassName = composedType is not null ? TranslateType(composedType) : ConventionServiceInstance.TranslateType(targetClassType); var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; - if (targetClassName.Equals(returnType, StringComparison.OrdinalIgnoreCase)) - return resultName; - if (targetClassType is CodeType currentType && - currentType.TypeDefinition is CodeClass definitionClass && - definitionClass.GetImmediateParentOfType() is CodeNamespace parentNamespace && - parentNamespace.FindChildByName(resultName) is CodeFunction factoryMethod) + if (ConventionServiceInstance.GetTypeString(targetClassType, currentElement, false, writer) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; + if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) { - var methodName = ConventionServiceInstance.GetTypeString(new CodeType + var factoryMethod = GetFactoryMethod(definitionClass, resultName); + if (factoryMethod != null) { - Name = resultName, - TypeDefinition = factoryMethod - }, currentElement, false, writer); - return methodName.ToFirstCharacterUpperCase();// static function is aliased + var methodName = ConventionServiceInstance.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); + return methodName.ToFirstCharacterUpperCase();// static function is aliased + } } - throw new InvalidOperationException($"Unable to find factory method for {targetClassName}"); + throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); + } + + private static T? GetParentOfTypeOrNull(CodeInterface definitionClass) where T : class + { + try + { + return definitionClass.GetImmediateParentOfType(); + } + catch (InvalidOperationException) + { + return null; + } + } + + private static CodeFunction? GetFactoryMethod(CodeInterface definitionClass, string factoryMethodName) + { + return GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName) + ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName); + } + + public static string GetDeserializationMethodName(CodeTypeBase codeType, CodeFunction codeFunction) + { + ArgumentNullException.ThrowIfNull(codeType); + ArgumentNullException.ThrowIfNull(codeFunction); + var isCollection = codeType.CollectionKind != CodeTypeCollectionKind.None; + var propertyType = ConventionServiceInstance.GetTypeString(codeType, codeFunction, false); + if (GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType) + { + return GetComposedTypeDeserializationMethodName(composedType); + } + if (codeType is CodeType currentType && !string.IsNullOrEmpty(propertyType)) + { + return (currentType.TypeDefinition, isCollection, propertyType) switch + { + (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", + (_, _, string prop) when ConventionServiceInstance.StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", + (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", + (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeType, codeFunction.OriginalLocalMethod)})", + _ => GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction) + }; + } + return GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction); + } + + private static string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeBase propType, string propertyTypeName, CodeFunction codeFunction) + { + return propertyTypeName switch + { + "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", + _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})" + }; } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 0fb1e03e27..cf9f398dea 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -20,5 +20,7 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName) AddOrReplaceCodeElementWriter(new CodeFileBlockEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService, clientNamespaceName)); AddOrReplaceCodeElementWriter(new CodeConstantWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeUnionTypeWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeIntersectionTypeWriter(conventionService)); } } From 5c43b9d54793c7b67f79ad117cc1342437becd2e Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 17 May 2024 13:22:09 +0300 Subject: [PATCH 015/117] increase coverage --- .../TypeScript/CodeComposedTypeBaseWriter.cs | 4 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 2 +- .../TypeScript/CodeIntersectionTypeWriter.cs | 6 +- .../Writers/TypeScript/CodeUnionTypeWriter.cs | 6 +- .../TypeScript/CodeFunctionWriterTests.cs | 117 ++++++++++++++++++ .../CodeIntersectionTypeWriterTests.cs | 66 ++++++++++ .../TypeScript/CodeUnionTypeWriterTests.cs | 66 ++++++++++ 7 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs create mode 100644 tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUnionTypeWriterTests.cs diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs index 6ee3a5c76b..39b5c8748a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; @@ -28,4 +28,4 @@ public override void WriteCodeElement(TCodeComposedTypeBase codeElement, Languag // TODO: documentation info writer.WriteLine($"export type {codeElement.Name.ToFirstCharacterUpperCase()} = {codeUnionString};"); } -} \ No newline at end of file +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 7d188ced5f..d670f42918 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -264,7 +264,7 @@ private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBas // handle union of primitive values if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { - return $"write{composedType.Name}Value"; + return $"write{ConventionServiceInstance.GetTypeString(composedType, composedType.Parent!, false).ToFirstCharacterUpperCase()}Value"; } // throw unsupported exception throw new InvalidOperationException($"Serialization for this composed type :: {composedType} :: is not supported yet"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs index ad3d1e392b..d5c6f9d59a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -1,4 +1,4 @@ -using Kiota.Builder.CodeDOM; +using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers.TypeScript; @@ -10,6 +10,6 @@ public CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) public override string TypesDelimiter { - get => " & "; + get => "&"; } -} \ No newline at end of file +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs index af289f13c3..2c4e42bc6c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs @@ -1,4 +1,4 @@ -using Kiota.Builder.CodeDOM; +using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers.TypeScript; @@ -10,6 +10,6 @@ public CodeUnionTypeWriter(TypeScriptConventionService conventionService) : base public override string TypesDelimiter { - get => " | "; + get => "|"; } -} \ No newline at end of file +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index f057dc872c..c18b79b6bd 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1,14 +1,21 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; using Kiota.Builder.Refiners; +using Kiota.Builder.Tests.OpenApiSampleFiles; using Kiota.Builder.Writers; +using Microsoft.Extensions.Logging; +using Moq; using Xunit; +using static Kiota.Builder.Refiners.TypeScriptRefiner; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Tests.Writers.TypeScript; public sealed class CodeFunctionWriterTests : IDisposable @@ -20,6 +27,9 @@ public sealed class CodeFunctionWriterTests : IDisposable private readonly CodeNamespace root; private const string MethodName = "methodName"; private const string ReturnTypeName = "Somecustomtype"; + private readonly HttpClient _httpClient = new(); + private readonly List _tempFiles = new(); + private const string IndexFileName = "index"; public CodeFunctionWriterTests() { @@ -30,6 +40,9 @@ public CodeFunctionWriterTests() } public void Dispose() { + foreach (var file in _tempFiles) + File.Delete(file); + _httpClient.Dispose(); tw?.Dispose(); GC.SuppressFinalize(this); } @@ -1118,4 +1131,108 @@ public async Task WritesConstructorWithEnumValue() var result = tw.ToString(); Assert.Contains($" ?? {codeEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up } + [Fact] + public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Github", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + Assert.NotNull(modelCodeFile); + + /* + * + * + \/** + * Creates a new instance of the appropriate class based on discriminator value + * @returns {ValidationError_errors_value} + *\/ + export function createValidationError_errors_valueFromDiscriminatorValue(node: ParseNode | undefined) : ValidationError_errors_value | undefined { + const nodeValue = node?.getNodeValue(); + switch (typeof nodeValue) { + case "number": + case "string": + return nodeValue; + default: + return undefined; + } + + */ + + // Test Factory function + var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null).FirstOrDefault(); + Assert.True(factoryFunction is not null); + writer.Write(factoryFunction); + var result = tw.ToString(); + Assert.Contains("return", result); + Assert.Contains("const", result); + Assert.Contains("switch (typeof", result); + Assert.Contains("case \"number\":", result); + Assert.Contains("case \"string\":", result); + Assert.Contains("return nodeValue;", result); + Assert.Contains("default", result); + Assert.Contains("return undefined;", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Github", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null).FirstOrDefault(); + Assert.True(serializerFunction is not null); + writer.Write(serializerFunction); + var serializerFunctionStr = tw.ToString(); + Assert.Contains("return", serializerFunctionStr); + Assert.Contains("switch", serializerFunctionStr); + Assert.Contains("case \"number\":", serializerFunctionStr); + Assert.Contains("case \"string\":", serializerFunctionStr); + Assert.Contains("break", serializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } } + diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs new file mode 100644 index 0000000000..0b8622194b --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using Kiota.Builder.CodeDOM; +using Moq; +using Xunit; + +namespace Kiota.Builder.Writers.TypeScript.Tests; +public sealed class CodeIntersectionTypeWriterTests : IDisposable +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeIntersectionTypeWriter codeElementWriter; + + public CodeIntersectionTypeWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); + codeElementWriter = new CodeIntersectionTypeWriter(new TypeScriptConventionService()); + tw = new StringWriter(); + writer.SetTextWriter(tw); + } + + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + + [Fact] + public void WriteCodeElement_ShouldThrowArgumentNullException_WhenCodeElementIsNull() + { + Assert.Throws(() => codeElementWriter.WriteCodeElement(null, writer)); + } + + [Fact] + public void WriteCodeElement_ShouldThrowArgumentNullException_WhenWriterIsNull() + { + var composedType = new Mock(); + Assert.Throws(() => codeElementWriter.WriteCodeElement(composedType.Object, null)); + } + + [Fact] + public void WriteCodeElement_ShouldThrowInvalidOperationException_WhenTypesIsEmpty() + { + var composedType = new Mock(); + Assert.Throws(() => codeElementWriter.WriteCodeElement(composedType.Object, writer)); + } + + [Fact] + public void WriteCodeElement_ShouldWriteCorrectOutput_WhenTypesIsNotEmpty() + { + CodeIntersectionType composedType = new CodeIntersectionType() { Name = "Test" }; + composedType.AddType(new CodeType { Name = "Type1" }); + composedType.AddType(new CodeType { Name = "Type2" }); + + var root = CodeNamespace.InitRootNamespace(); + var ns = root.AddNamespace("graphtests.models"); + ns.TryAddCodeFile(DefaultPath, composedType); + + codeElementWriter.WriteCodeElement(composedType, writer); + + var result = tw.ToString(); + Assert.Contains("export type Test = Type1 & Type2;", result); + } +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUnionTypeWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUnionTypeWriterTests.cs new file mode 100644 index 0000000000..84f5e46104 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUnionTypeWriterTests.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using Kiota.Builder.CodeDOM; +using Moq; +using Xunit; + +namespace Kiota.Builder.Writers.TypeScript.Tests; +public sealed class CodeUnionTypeWriterTests : IDisposable +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeUnionTypeWriter codeElementWriter; + + public CodeUnionTypeWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); + codeElementWriter = new CodeUnionTypeWriter(new TypeScriptConventionService()); + tw = new StringWriter(); + writer.SetTextWriter(tw); + } + + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + + [Fact] + public void WriteCodeElement_ShouldThrowArgumentNullException_WhenCodeElementIsNull() + { + Assert.Throws(() => codeElementWriter.WriteCodeElement(null, writer)); + } + + [Fact] + public void WriteCodeElement_ShouldThrowArgumentNullException_WhenWriterIsNull() + { + var composedType = new Mock(); + Assert.Throws(() => codeElementWriter.WriteCodeElement(composedType.Object, null)); + } + + [Fact] + public void WriteCodeElement_ShouldThrowInvalidOperationException_WhenTypesIsEmpty() + { + var composedType = new Mock(); + Assert.Throws(() => codeElementWriter.WriteCodeElement(composedType.Object, writer)); + } + + [Fact] + public void WriteCodeElement_ShouldWriteCorrectOutput_WhenTypesIsNotEmpty() + { + CodeUnionType composedType = new CodeUnionType() { Name = "Test" }; + composedType.AddType(new CodeType { Name = "Type1" }); + composedType.AddType(new CodeType { Name = "Type2" }); + + var root = CodeNamespace.InitRootNamespace(); + var ns = root.AddNamespace("graphtests.models"); + ns.TryAddCodeFile(DefaultPath, composedType); + + codeElementWriter.WriteCodeElement(composedType, writer); + + var result = tw.ToString(); + Assert.Contains("export type Test = Type1 | Type2;", result); + } +} From 7b3a92ae3a7baf1f0144f1a79e74d6209f0f23a2 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 17 May 2024 16:10:08 +0300 Subject: [PATCH 016/117] increase code coverage --- .../TypeScript/TypeScriptConventionService.cs | 15 ++---- .../TypeScript/CodeFunctionWriterTests.cs | 1 - .../TypeScriptConventionServiceTests.cs | 48 +++++++++++++++++++ 3 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 9d3e44846d..930565744d 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -108,16 +108,6 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen return $"{typeName}{collectionSuffix}{genericParameters}"; } - private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) - { - return codeComposedTypeBase switch - { - CodeUnionType _ => " | ", - CodeIntersectionType _ => " & ", - _ => throw new InvalidOperationException("unknown composed type"), - }; - } - private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { if (targetElement.GetImmediateParentOfType() is IBlock parentBlock && @@ -251,11 +241,12 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); } - private static T? GetParentOfTypeOrNull(CodeInterface definitionClass) where T : class + public static T? GetParentOfTypeOrNull(CodeElement codeElement) where T : class { + ArgumentNullException.ThrowIfNull(codeElement); try { - return definitionClass.GetImmediateParentOfType(); + return codeElement.GetImmediateParentOfType(); } catch (InvalidOperationException) { diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index c18b79b6bd..e6dca21b50 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -15,7 +15,6 @@ using Moq; using Xunit; using static Kiota.Builder.Refiners.TypeScriptRefiner; -using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Tests.Writers.TypeScript; public sealed class CodeFunctionWriterTests : IDisposable diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs new file mode 100644 index 0000000000..ff4df5bf19 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -0,0 +1,48 @@ +using System; +using Kiota.Builder; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Writers.TypeScript; +using Xunit; +using static Kiota.Builder.CodeDOM.CodeTypeBase; + +namespace Kiota.Builder.Tests.Writers.TypeScript; + +public class TypeScriptConventionServiceTests +{ + [Fact] + public void GetComposedTypeDeserializationMethodName_ShouldThrowArgumentNullException_WhenComposedTypeIsNull() + { + Assert.Throws(() => TypeScriptConventionService.GetComposedTypeDeserializationMethodName(null)); + } + + [Theory] + [InlineData(CodeTypeCollectionKind.None, "composedType", "getComposedType")] + [InlineData(CodeTypeCollectionKind.Array, "composedType", "getCollectionOfComposedType")] + public void GetComposedTypeDeserializationMethodName_ShouldReturnCorrectName_WhenComposedTypeIsNotNull(CodeTypeCollectionKind collectionKind, string name, string expectedName) + { + var composedType = new CodeUnionType { CollectionKind = collectionKind, Name = name }; + var result = TypeScriptConventionService.GetComposedTypeDeserializationMethodName(composedType); + Assert.Equal(expectedName, result); + } + + [Fact] + public void GetParentOfTypeOrNull_ShouldThrowArgumentNullException_WhenCodeElementIsNull() + { + Assert.Throws(() => TypeScriptConventionService.GetParentOfTypeOrNull(null)); + } + + [Fact] + public void GetParentOfTypeOrNull_ShouldReturnNull_WhenNoParentOfTypeExists() + { + var codeElement = new CodeProperty { Name = "Test Property", Type = new CodeType { Name = "test" } }; + Assert.Null(TypeScriptConventionService.GetParentOfTypeOrNull(codeElement)); + } + + [Fact] + public void GetParentOfTypeOrNull_ShouldReturnParent_WhenParentOfTypeExists() + { + var parent = new CodeClass(); + var codeElement = new CodeProperty { Name = "Test Property", Parent = parent, Type = new CodeType { Name = "test" } }; + Assert.Equal(parent, TypeScriptConventionService.GetParentOfTypeOrNull(codeElement)); + } +} From 663798e7aad2ce1ba98e055f885c46c93d918c55 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 21 May 2024 23:38:51 +0300 Subject: [PATCH 017/117] handle union of objects in typescript --- .../Refiners/TypeScriptRefiner.cs | 82 ++++---- .../Writers/TypeScript/CodeFunctionWriter.cs | 187 +++++++++++------- .../TypeScript/TypeScriptConventionService.cs | 38 ++-- .../TypeScriptConventionServiceTests.cs | 16 -- 4 files changed, 171 insertions(+), 152 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 312afeba79..392298eb6c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -295,10 +295,11 @@ private static bool IsRelevantFactory(CodeFunction codeFunction, CodeInterface c private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, CodeFunction[] functions) { - var children = new List(functions); - - // Add the composed type, The writer will output the composed type as a type definition e.g export type Pet = Cat | Dog - children.Add(composedType); + var children = new List(functions) + { + // Add the composed type, The writer will output the composed type as a type definition e.g export type Pet = Cat | Dog + composedType + }; ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); @@ -309,12 +310,13 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var factoryMethods = children.OfType().Where(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory).ToList(); + var factoryMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory); - foreach (var factoryMethod in factoryMethods) + if (factoryMethod is not null) { var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); - var factoryFunction = new CodeFunction(method) { Name = method.Name }; + var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; + factoryFunction.AddUsing(factoryMethod.Usings.ToArray()); children.Remove(factoryMethod); RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, factoryMethod); @@ -324,29 +326,26 @@ private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterf private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var serializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); - if (serializerMethod is not null) + var function = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); + if (function is not null) { - var method = CreateSerializerMethodForComposedType(codeInterface, composedType, serializerMethod); + var method = CreateSerializerMethodForComposedType(codeInterface, composedType, function); var serializerFunction = new CodeFunction(method) { Name = method.Name }; + serializerFunction.AddUsing(function.Usings.ToArray()); - children.Remove(serializerMethod); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, serializerMethod); + children.Remove(function); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, function); children.Add(serializerFunction); } } private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - // Completely remove the deserializer method if the composed type is a union of primitive values - if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); + if (deserializerMethod is not null) { - var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); - if (deserializerMethod is not null) - { - children.Remove(deserializerMethod); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); - } + children.Remove(deserializerMethod); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); } } @@ -356,29 +355,41 @@ private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface co codeNamespace.RemoveChildElement(function); } - private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) { - var method = CreateCodeMethod(codeInterface, composedTypeBase, function); - method.ReturnType = composedTypeBase; - method.AddParameter(CreateParseNodeCodeParameter()); + var method = CreateCodeMethod(codeInterface, function); + if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + method.ReturnType = composedType; return method; } private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) { - var method = CreateCodeMethod(codeInterface, composedType, function); + var method = CreateCodeMethod(codeInterface, function); // Add the key parameter if the composed type is a union of primitive values - if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) - method.AddParameter(CreateKeyParameter()); + method.AddParameter(CreateKeyParameter()); method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); return method; } - private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComposedTypeBase composedTypeBase, CodeFunction function) + private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) + { + var method = CreateCodeMethod(codeInterface, function); + // Add the key parameter if the composed type is a union of primitive values + if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + { + method.ReturnType = new CodeType { Name = composedType.Name, IsExternal = false, TypeDefinition = composedType }; + method.ClearParameters(); + method.AddParameter(CreateParseNodeCodeParameter()); + } + return method; + } + + private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeFunction function) { var method = new CodeMethod { - Name = GetSerializerOrDeserializerMethodName(composedTypeBase, function), + Name = function.OriginalLocalMethod.Name, ReturnType = function.OriginalLocalMethod.ReturnType, Kind = GetComposedTypeMethodKind(function), Access = function.OriginalLocalMethod.Access, @@ -388,6 +399,8 @@ private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeComp Parent = codeInterface.OriginalClass, }; + method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); + return method; } @@ -402,16 +415,6 @@ private static CodeMethodKind GetComposedTypeMethodKind(CodeFunction function) }; } - private static string GetSerializerOrDeserializerMethodName(CodeComposedTypeBase composedTypeBase, CodeFunction function) - { - return function.OriginalLocalMethod.Kind switch - { - CodeMethodKind.Factory => GetFactoryMethodName(composedTypeBase, function), - CodeMethodKind.Serializer => GetSerializationMethodName(composedTypeBase), - _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") - }; - } - private static CodeParameter CreateParseNodeCodeParameter() { return new CodeParameter @@ -456,6 +459,7 @@ private static CodeParameter CreateKeyParameter() { return codeType?.TypeDefinition switch { + CodeComposedTypeBase composedType => composedType, CodeInterface ci => GetOriginalComposedType(ci), CodeClass cc => GetOriginalComposedType(cc), _ => null, diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index d670f42918..84e73ce58f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -27,8 +27,12 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; - var returnType = codeMethod.Kind is CodeMethodKind.Factory ? - "((instance?: Parsable) => Record void>)" : + var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is CodeComposedTypeBase composedType && conventions.IsComposedOfPrimitives(composedType); + + var factoryMethodReturnType = "((instance?: Parsable) => Record void>)"; + + var returnType = codeMethod.Kind is CodeMethodKind.Factory || (codeMethod.Kind is CodeMethodKind.ComposedTypeFactory && !isComposedOfPrimitives) ? + factoryMethodReturnType : conventions.GetTypeString(codeMethod.ReturnType, codeElement); var isVoid = "void".EqualsIgnoreCase(returnType); CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.OriginalLocalMethod, writer, isVoid, conventions); @@ -45,14 +49,12 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w WriteSerializerFunction(codeElement, writer); break; case CodeMethodKind.Factory: + case CodeMethodKind.ComposedTypeFactory: WriteDiscriminatorFunction(codeElement, writer); break; case CodeMethodKind.ClientConstructor: WriteApiConstructorBody(parentFile, codeMethod, writer); break; - case CodeMethodKind.ComposedTypeFactory: - WriteComposedTypeFactory(codeMethod, writer); - break; case CodeMethodKind.ComposedTypeSerializer: WriteComposedTypeSerializer(codeMethod, writer); break; @@ -60,26 +62,23 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } - private void WriteComposedTypeFactory(CodeMethod method, LanguageWriter writer) + private static void WriteFactoryMethodBodyForUnionOfPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) { - // TODO: Add implementation for object types and collections - if (GetOriginalComposedType(method.ReturnType) is CodeComposedTypeBase composedType) + ArgumentNullException.ThrowIfNull(parseNodeParameter); + writer.WriteLine($"const nodeValue = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}?.getNodeValue();"); + writer.StartBlock($"switch (typeof nodeValue) {{"); + foreach (var type in composedType.Types) { - writer.WriteLine("const nodeValue = node?.getNodeValue();"); - writer.StartBlock($"switch (typeof nodeValue) {{"); - foreach (var type in composedType.Types) - { - var nodeType = conventions.GetTypeString(type, method, false); - writer.WriteLine($"case \"{nodeType}\":"); - } - writer.IncreaseIndent(); - writer.WriteLine($"return nodeValue;"); - writer.DecreaseIndent(); - writer.StartBlock($"default:"); - writer.WriteLine($"return undefined;"); - writer.DecreaseIndent(); - writer.CloseBlock(); + var nodeType = ConventionServiceInstance.GetTypeString(type, codeElement, false); + writer.WriteLine($"case \"{nodeType}\":"); } + writer.IncreaseIndent(); + writer.WriteLine($"return nodeValue;"); + writer.DecreaseIndent(); + writer.StartBlock($"default:"); + writer.WriteLine($"return undefined;"); + writer.DecreaseIndent(); + writer.CloseBlock(); } private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter writer) @@ -87,28 +86,37 @@ private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter write var composedParam = method.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); if (composedParam == null) return; - if (GetOriginalComposedType(composedParam) is CodeComposedTypeBase composedType) - { - var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} == undefined) return;"); - writer.StartBlock($"switch (typeof {paramName}) {{"); + if (GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; - foreach (var type in composedType.Types) - { - var nodeType = conventions.GetTypeString(type, method, false); - var serializationName = GetSerializationMethodName(type); - if (serializationName == null || nodeType == null) continue; - - writer.StartBlock($"case \"{nodeType}\":"); - writer.WriteLine($"writer.{serializationName}(key, {paramName});"); - writer.WriteLine($"break;"); - writer.DecreaseIndent(); - } + WriteComposedTypeSerialization(composedType, composedParam, method, writer); + } + + private void WriteComposedTypeSerialization(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeMethod method, LanguageWriter writer) + { + var paramName = composedParam.Name.ToFirstCharacterLowerCase(); + writer.WriteLine($"if ({paramName} == undefined) return;"); + writer.StartBlock($"switch (typeof {paramName}) {{"); + + foreach (var type in composedType.Types) + { + WriteTypeSerialization(type, paramName, method, writer); } writer.CloseBlock(); } + private void WriteTypeSerialization(CodeTypeBase type, string paramName, CodeMethod method, LanguageWriter writer) + { + var nodeType = conventions.GetTypeString(type, method, false); + var serializationName = GetSerializationMethodName(type, method); + if (serializationName == null || nodeType == null) return; + + writer.StartBlock($"case \"{nodeType}\":"); + writer.WriteLine($"writer.{serializationName}(key, {paramName});"); + writer.WriteLine($"break;"); + writer.DecreaseIndent(); + } + private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod method, LanguageWriter writer) { WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); @@ -151,33 +159,73 @@ private void WriteDiscriminatorFunction(CodeFunction codeElement, LanguageWriter private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) { var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); - if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType && parseNodeParameter != null) + var composedType = GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType); + if (composedType is CodeUnionType codeUnion && ConventionServiceInstance.IsComposedOfPrimitives(codeUnion)) { - writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}.getChildNode(\"{codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName}\");", - "if (mappingValueNode) {"); - writer.IncreaseIndent(); - writer.WriteLines("const mappingValue = mappingValueNode.getStringValue();", - "if (mappingValue) {"); - writer.IncreaseIndent(); - - writer.StartBlock("switch (mappingValue) {"); - foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) - { - writer.StartBlock($"case \"{mappedType.Key}\":"); - writer.WriteLine($"return {GetDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); - writer.DecreaseIndent(); - } - writer.CloseBlock(); - writer.CloseBlock(); - writer.CloseBlock(); + WriteFactoryMethodBodyForUnionOfPrimitives(codeUnion, codeElement, writer, parseNodeParameter); + return; + } + + if (ShouldWriteDiscriminatorInformation(codeElement, composedType) && parseNodeParameter != null) + { + WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); + } + + if (composedType is null) + { + WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); + return; + } + + // It's a composed type but there isn't a discriminator property + writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + } + + private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) + { + var deserializationFunction = GetDeserializationFunction(codeElement, returnType); + var parseNodeParameterForPrimitiveValues = GetParseNodeParameterForPrimitiveValues(codeElement, parseNodeParameter); + writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()}{parseNodeParameterForPrimitiveValues};"); + } + + private bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement, CodeComposedTypeBase? composedType) + { + return codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType || composedType is CodeUnionType; + } + + private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParameter parseNodeParameter, LanguageWriter writer) + { + writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}.getChildNode(\"{codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName}\");", + "if (mappingValueNode) {"); + writer.IncreaseIndent(); + writer.WriteLines("const mappingValue = mappingValueNode.getStringValue();", + "if (mappingValue) {"); + writer.IncreaseIndent(); + + writer.StartBlock("switch (mappingValue) {"); + foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) + { + writer.StartBlock($"case \"{mappedType.Key}\":"); + writer.WriteLine($"return {GetDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); + writer.DecreaseIndent(); } - var s = GetDeserializationFunction(codeElement, returnType); - writer.WriteLine($"return {s.ToFirstCharacterLowerCase()};"); + writer.CloseBlock(); + writer.CloseBlock(); + writer.CloseBlock(); + } + + private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, CodeParameter? parseNodeParameter) + { + if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is CodeComposedTypeBase composedType && conventions.IsComposedOfPrimitives(composedType) && parseNodeParameter is not null) + { + return $"({parseNodeParameter.Name.ToFirstCharacterLowerCase()})"; + } + return string.Empty; } private string GetDeserializationFunction(CodeElement codeElement, string returnType) { - var codeNamespace = codeElement.GetImmediateParentOfType(); + var codeNamespace = codeElement.GetImmediateParentOfType().GetRootNamespace(); var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); @@ -220,7 +268,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var propTypeName = conventions.GetTypeString(codeProperty.Type, codeProperty.Parent!, false); - var serializationName = GetSerializationMethodName(codeProperty.Type); + var serializationName = GetSerializationMethodName(codeProperty.Type, codeFunction.OriginalLocalMethod); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; var composedType = GetOriginalComposedType(codeProperty.Type); @@ -243,31 +291,28 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro } } - public static string GetSerializationMethodName(CodeTypeBase propertyType) + public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { + ArgumentNullException.ThrowIfNull(propertyType); + ArgumentNullException.ThrowIfNull(method); var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); var composedType = GetOriginalComposedType(propertyType); var currentType = propertyType as CodeType; return (composedType, currentType, propertyTypeName) switch { - (CodeComposedTypeBase type, _, _) => GetComposedTypeSerializationMethodName(type), + (CodeComposedTypeBase type, _, _) => GetComposedTypeSerializationMethodName(type, method), (_, CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, (_, _, "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration") => $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value", _ => "writeObjectValue" }; } - private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType) + private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType, CodeMethod method) { + ArgumentNullException.ThrowIfNull(method); ArgumentNullException.ThrowIfNull(composedType); - // handle union of primitive values - if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) - { - return $"write{ConventionServiceInstance.GetTypeString(composedType, composedType.Parent!, false).ToFirstCharacterUpperCase()}Value"; - } - // throw unsupported exception - throw new InvalidOperationException($"Serialization for this composed type :: {composedType} :: is not supported yet"); + return $"serialize{ConventionServiceInstance.GetTypeString(composedType, method)}"; } private static string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) @@ -337,7 +382,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str else { var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)}{defaultValueSuffix};{suffix} }},"); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 930565744d..ff90df6aa8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -210,19 +210,6 @@ private string GetDeprecationComment(IDeprecableElement element) return $"@deprecated {element.Deprecation.GetDescription(type => GetTypeString(type, (element as CodeElement)!))}{versionComment}{dateComment}{removalComment}"; } - public static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType) - { - ArgumentNullException.ThrowIfNull(composedType); - var isCollection = composedType.CollectionKind != CodeTypeCollectionKind.None; - return GetComposedTypeDeserializationMethodName(composedType, isCollection); - } - - private static string GetComposedTypeDeserializationMethodName(CodeComposedTypeBase composedType, bool isCollection) - { - var propertyName = composedType.Name.ToFirstCharacterUpperCase(); - return isCollection ? $"getCollectionOf{propertyName}" : $"get{propertyName}"; - } - public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter? writer = null) { var composedType = GetOriginalComposedType(targetClassType); @@ -260,36 +247,35 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName); } - public static string GetDeserializationMethodName(CodeTypeBase codeType, CodeFunction codeFunction) + public static string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method) { ArgumentNullException.ThrowIfNull(codeType); - ArgumentNullException.ThrowIfNull(codeFunction); + ArgumentNullException.ThrowIfNull(method); var isCollection = codeType.CollectionKind != CodeTypeCollectionKind.None; - var propertyType = ConventionServiceInstance.GetTypeString(codeType, codeFunction, false); - if (GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType) - { - return GetComposedTypeDeserializationMethodName(composedType); - } - if (codeType is CodeType currentType && !string.IsNullOrEmpty(propertyType)) + var propertyType = ConventionServiceInstance.GetTypeString(codeType, method, false); + + CodeTypeBase _codeType = GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : codeType; + + if (_codeType is CodeType currentType && !string.IsNullOrEmpty(propertyType)) { return (currentType.TypeDefinition, isCollection, propertyType) switch { (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", (_, _, string prop) when ConventionServiceInstance.StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", - (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeType, codeFunction.OriginalLocalMethod)})", - _ => GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction) + (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(_codeType, method)})", + _ => GetDeserializationMethodNameForPrimitiveOrObject(_codeType, propertyType, method) }; } - return GetDeserializationMethodNameForPrimitiveOrObject(codeType, propertyType, codeFunction); + return GetDeserializationMethodNameForPrimitiveOrObject(_codeType, propertyType, method); } - private static string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeBase propType, string propertyTypeName, CodeFunction codeFunction) + private static string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeBase propType, string propertyTypeName, CodeMethod method) { return propertyTypeName switch { "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", - _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, codeFunction.OriginalLocalMethod)})" + _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, method)})" }; } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index ff4df5bf19..0c200931b2 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -9,22 +9,6 @@ namespace Kiota.Builder.Tests.Writers.TypeScript; public class TypeScriptConventionServiceTests { - [Fact] - public void GetComposedTypeDeserializationMethodName_ShouldThrowArgumentNullException_WhenComposedTypeIsNull() - { - Assert.Throws(() => TypeScriptConventionService.GetComposedTypeDeserializationMethodName(null)); - } - - [Theory] - [InlineData(CodeTypeCollectionKind.None, "composedType", "getComposedType")] - [InlineData(CodeTypeCollectionKind.Array, "composedType", "getCollectionOfComposedType")] - public void GetComposedTypeDeserializationMethodName_ShouldReturnCorrectName_WhenComposedTypeIsNotNull(CodeTypeCollectionKind collectionKind, string name, string expectedName) - { - var composedType = new CodeUnionType { CollectionKind = collectionKind, Name = name }; - var result = TypeScriptConventionService.GetComposedTypeDeserializationMethodName(composedType); - Assert.Equal(expectedName, result); - } - [Fact] public void GetParentOfTypeOrNull_ShouldThrowArgumentNullException_WhenCodeElementIsNull() { From 8c6e25bae23ff88e1b0e155f5c6c58a01d8d7c3a Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 21 May 2024 23:51:30 +0300 Subject: [PATCH 018/117] remove unused code --- .../Refiners/TypeScriptRefiner.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 392298eb6c..c92567a209 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -372,19 +372,6 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co return method; } - private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) - { - var method = CreateCodeMethod(codeInterface, function); - // Add the key parameter if the composed type is a union of primitive values - if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) - { - method.ReturnType = new CodeType { Name = composedType.Name, IsExternal = false, TypeDefinition = composedType }; - method.ClearParameters(); - method.AddParameter(CreateParseNodeCodeParameter()); - } - return method; - } - private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeFunction function) { var method = new CodeMethod @@ -415,19 +402,6 @@ private static CodeMethodKind GetComposedTypeMethodKind(CodeFunction function) }; } - private static CodeParameter CreateParseNodeCodeParameter() - { - return new CodeParameter - { - Name = "node", - Type = new CodeType - { - Name = "ParseNode", - IsExternal = true, - }, - }; - } - private static CodeParameter CreateKeyParameter() { return new CodeParameter From 5e41e0dab7633d42759ce9ccec69aa5ef3c859e6 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 22 May 2024 14:51:23 +0300 Subject: [PATCH 019/117] add serialization for union objects --- .../Writers/TypeScript/CodeFunctionWriter.cs | 64 ++++++++++++++----- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 84e73ce58f..0d4ea1d3b8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -10,11 +10,8 @@ namespace Kiota.Builder.Writers.TypeScript; -public class CodeFunctionWriter : BaseElementWriter +public class CodeFunctionWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) { - public CodeFunctionWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } private static readonly HashSet customSerializationWriters = new(StringComparer.OrdinalIgnoreCase) { "writeObjectValue", "writeCollectionOfObjectValues" }; public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter writer) @@ -56,7 +53,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w WriteApiConstructorBody(parentFile, codeMethod, writer); break; case CodeMethodKind.ComposedTypeSerializer: - WriteComposedTypeSerializer(codeMethod, writer); + WriteComposedTypeSerializer(codeElement, writer); break; default: throw new InvalidOperationException("Invalid code method kind"); } @@ -81,17 +78,42 @@ private static void WriteFactoryMethodBodyForUnionOfPrimitives(CodeComposedTypeB writer.CloseBlock(); } - private void WriteComposedTypeSerializer(CodeMethod method, LanguageWriter writer) + private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer) { - var composedParam = method.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); + var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); if (composedParam == null) return; if (GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; - WriteComposedTypeSerialization(composedType, composedParam, method, writer); + if (conventions.IsComposedOfPrimitives(composedType)) + { + WriteComposedTypeSerializationForPrimitives(composedType, composedParam, codeElement, writer); + return; + } + + WriteComposedTypeSerialization(composedType, composedParam, codeElement, writer); } - private void WriteComposedTypeSerialization(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeMethod method, LanguageWriter writer) + private void WriteComposedTypeSerialization(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + { + var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); + var paramName = composedParam.Name.ToFirstCharacterLowerCase(); + writer.WriteLine($"if ({paramName} == undefined) return;"); + writer.StartBlock($"switch ({paramName}.{discriminatorPropertyName}) {{"); + + foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) + { + writer.StartBlock($"case \"{mappedType.Key}\":"); + var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); + writer.WriteLine($"writer.writeObjectValue<{mappedTypeName}>(key, {paramName}, {GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)});"); + writer.WriteLine("break;"); + writer.DecreaseIndent(); + } + + writer.CloseBlock(); + } + + private void WriteComposedTypeSerializationForPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"if ({paramName} == undefined) return;"); @@ -105,10 +127,10 @@ private void WriteComposedTypeSerialization(CodeComposedTypeBase composedType, C writer.CloseBlock(); } - private void WriteTypeSerialization(CodeTypeBase type, string paramName, CodeMethod method, LanguageWriter writer) + private void WriteTypeSerialization(CodeTypeBase type, string paramName, CodeFunction method, LanguageWriter writer) { var nodeType = conventions.GetTypeString(type, method, false); - var serializationName = GetSerializationMethodName(type, method); + var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod); if (serializationName == null || nodeType == null) return; writer.StartBlock($"case \"{nodeType}\":"); @@ -183,7 +205,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) { - var deserializationFunction = GetDeserializationFunction(codeElement, returnType); + var deserializationFunction = GetFunctionName(codeElement, returnType, CodeMethodKind.Deserializer); var parseNodeParameterForPrimitiveValues = GetParseNodeParameterForPrimitiveValues(codeElement, parseNodeParameter); writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()}{parseNodeParameterForPrimitiveValues};"); } @@ -206,7 +228,7 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) { writer.StartBlock($"case \"{mappedType.Key}\":"); - writer.WriteLine($"return {GetDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); + writer.WriteLine($"return {GetFunctionName(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase(), CodeMethodKind.Deserializer)};"); writer.DecreaseIndent(); } writer.CloseBlock(); @@ -223,12 +245,22 @@ private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, return string.Empty; } - private string GetDeserializationFunction(CodeElement codeElement, string returnType) + private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind) { var codeNamespace = codeElement.GetImmediateParentOfType().GetRootNamespace(); - var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; + var functionName = GetFunctionName(returnType, kind); + var parent = codeNamespace.FindChildByName(functionName); + return ConventionServiceInstance.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); + } - return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); + private static string GetFunctionName(string returnType, CodeMethodKind functionKind) + { + return functionKind switch + { + CodeMethodKind.Serializer => $"serialize{returnType}", + CodeMethodKind.Deserializer => $"deserializeInto{returnType}", + _ => throw new InvalidOperationException($"Unsupported function kind :: {functionKind}") + }; } private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) From 15a514016a4cc6ee7d5d61234b1917e85204fd22 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 22 May 2024 21:59:13 +0300 Subject: [PATCH 020/117] fix failing test and improve coverage --- .../Writers/TypeScript/CodeFunctionWriter.cs | 21 +++++++--- .../TypeScript/CodeFunctionWriterTests.cs | 41 +++++++++++++++++++ .../TypeScriptConventionServiceTests.cs | 16 +++++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 0d4ea1d3b8..24a60863c6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -91,10 +91,10 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite return; } - WriteComposedTypeSerialization(composedType, composedParam, codeElement, writer); + WriteComposedTypeSerialization(composedParam, codeElement, writer); } - private void WriteComposedTypeSerialization(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); var paramName = composedParam.Name.ToFirstCharacterLowerCase(); @@ -247,10 +247,21 @@ private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind) { - var codeNamespace = codeElement.GetImmediateParentOfType().GetRootNamespace(); var functionName = GetFunctionName(returnType, kind); - var parent = codeNamespace.FindChildByName(functionName); - return ConventionServiceInstance.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); + var parentNamespace = codeElement.GetImmediateParentOfType(); + var codeFunction = FindCodeFunctionInParentNamespaces(functionName, parentNamespace); + return ConventionServiceInstance.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); + } + + private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) + { + CodeFunction? codeFunction; + do + { + codeFunction = parentNamespace?.FindChildByName(functionName); + parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); + } while (codeFunction is null && parentNamespace is not null); + return codeFunction; } private static string GetFunctionName(string returnType, CodeMethodKind functionKind) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index e6dca21b50..29c9ffc470 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1233,5 +1233,46 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() Assert.Contains("break", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } + + [Fact] + public async Task Writes_UnionOfObjects_SerializerFunctions() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Pets", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pets"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("petsRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null).FirstOrDefault(); + Assert.True(serializerFunction is not null); + writer.Write(serializerFunction); + var serializerFunctionStr = tw.ToString(); + Assert.Contains("return", serializerFunctionStr); + Assert.Contains("switch", serializerFunctionStr); + Assert.Contains("case \"Cat\":", serializerFunctionStr); + Assert.Contains("case \"Dog\":", serializerFunctionStr); + Assert.Contains("break", serializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index 0c200931b2..dc5ee29954 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -1,9 +1,7 @@ using System; -using Kiota.Builder; using Kiota.Builder.CodeDOM; using Kiota.Builder.Writers.TypeScript; using Xunit; -using static Kiota.Builder.CodeDOM.CodeTypeBase; namespace Kiota.Builder.Tests.Writers.TypeScript; @@ -29,4 +27,18 @@ public void GetParentOfTypeOrNull_ShouldReturnParent_WhenParentOfTypeExists() var codeElement = new CodeProperty { Name = "Test Property", Parent = parent, Type = new CodeType { Name = "test" } }; Assert.Equal(parent, TypeScriptConventionService.GetParentOfTypeOrNull(codeElement)); } + + [Fact] + public void TranslateType_ThrowsArgumentNullException_WhenComposedTypeIsNull() + { + Assert.Throws(() => TypeScriptConventionService.TranslateType(null)); + } + + [Fact] + public void TranslateType_ReturnsCorrectTranslation_WhenComposedTypeIsNotNull() + { + var composedType = new CodeUnionType { Name = "test" }; + var result = TypeScriptConventionService.TranslateType(composedType); + Assert.Equal("Test", result); + } } From 9b27708adfad6ff7ff61e76435b357a28968116c Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 22 May 2024 22:13:56 +0300 Subject: [PATCH 021/117] add pets union test yml file --- .../OpenApiSampleFiles/PetsUnion.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs new file mode 100644 index 0000000000..cd1eea9b86 --- /dev/null +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs @@ -0,0 +1,66 @@ +namespace Kiota.Builder.Tests.OpenApiSampleFiles; + + +public static class PetsUnion +{ + public static readonly string OpenApiYaml = @" +openapi: 3.0.0 +info: + title: Pet API + version: 1.0.0 +paths: + /pets: + patch: + summary: Update a pet + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + discriminator: + propertyName: pet_type + responses: + '200': + description: Updated +components: + schemas: + Pet: + type: object + required: + - pet_type + properties: + pet_type: + type: string + discriminator: + propertyName: pet_type + Dog: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + bark: + type: boolean + breed: + type: string + enum: [Dingo, Husky, Retriever, Shepherd] + required: + - pet_type + - bark + - breed + Cat: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + hunts: + type: boolean + age: + type: integer + required: + - pet_type + - hunts + - age"; +} From d61a5be478044f63f998c89f130dab8b9e5ea90b Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 23 May 2024 11:50:49 +0300 Subject: [PATCH 022/117] remove unused parameter --- .../Refiners/TypeScriptRefiner.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index c92567a209..d1d6976557 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -302,8 +302,8 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac }; ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); + ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, children); + ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, children); return children; } @@ -324,12 +324,12 @@ private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterf } } - private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, List children) { var function = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); if (function is not null) { - var method = CreateSerializerMethodForComposedType(codeInterface, composedType, function); + var method = CreateSerializerMethodForComposedType(codeInterface, function); var serializerFunction = new CodeFunction(method) { Name = method.Name }; serializerFunction.AddUsing(function.Usings.ToArray()); @@ -339,7 +339,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInt } } - private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, List children) { var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); if (deserializerMethod is not null) @@ -363,7 +363,7 @@ private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeI return method; } - private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) + private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) { var method = CreateCodeMethod(codeInterface, function); // Add the key parameter if the composed type is a union of primitive values @@ -442,11 +442,7 @@ private static CodeParameter CreateKeyParameter() public static CodeComposedTypeBase? GetOriginalComposedType(CodeInterface codeInterface) { - if (codeInterface?.OriginalClass is CodeClass originalClass) - { - return GetOriginalComposedType(originalClass); - } - return null; + return codeInterface?.OriginalClass is CodeClass originalClass ? GetOriginalComposedType(originalClass) : null; } public static CodeComposedTypeBase? GetOriginalComposedType(CodeClass codeClass) From f44220bb61968d3e24a01224e1a10be8916d9754 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 28 May 2024 18:15:35 +0300 Subject: [PATCH 023/117] retrieve serialization method correctly --- .../Writers/TypeScript/CodeFunctionWriter.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 24a60863c6..e12fead36c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -105,7 +105,8 @@ private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFun { writer.StartBlock($"case \"{mappedType.Key}\":"); var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"writer.writeObjectValue<{mappedTypeName}>(key, {paramName}, {GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)});"); + var serializationName = GetSerializationMethodName(mappedType.Value, codeElement.OriginalLocalMethod); + writer.WriteLine($"writer.{serializationName}<{mappedTypeName}>(key, {paramName}, {GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)});"); writer.WriteLine("break;"); writer.DecreaseIndent(); } @@ -260,7 +261,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM { codeFunction = parentNamespace?.FindChildByName(functionName); parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); - } while (codeFunction is null && parentNamespace is not null); + } while (codeFunction?.Name != functionName && parentNamespace is not null); return codeFunction; } @@ -355,6 +356,23 @@ private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBas { ArgumentNullException.ThrowIfNull(method); ArgumentNullException.ThrowIfNull(composedType); + /* + Serialization will be delegated into a generated method because we cant tell in advance, Using the Pets example, whether its a Cat or Dog + without inspecting the discriminator property which only the generator can tell, so instead of writer.writeObjectValue("pet", responseObject.pet, serializePet) + * it will be something like: + + export function serializePet(writer: SerializationWriter, key: string, pet: Partial | undefined = {}) : void { + if (pet == undefined) return; + switch (pet.pet_type) { + case "Cat": + writer.writeObjectValue(key, pet, serializeCat); + break; + case "Dog": + writer.writeObjectValue(key, pet, serializeDog); + break; + } + } + */ return $"serialize{ConventionServiceInstance.GetTypeString(composedType, method)}"; } From 14ee8cb51af8f49c00a630d8e576a65e92953020 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 29 May 2024 13:15:41 +0300 Subject: [PATCH 024/117] rectify the serilaization function for CodeUnion types --- .../Refiners/TypeScriptRefiner.cs | 11 +++--- .../Writers/TypeScript/CodeFunctionWriter.cs | 38 ++++++------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index d1d6976557..3db5738427 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -302,7 +302,7 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac }; ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, children); + ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, children); return children; @@ -324,12 +324,12 @@ private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterf } } - private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, List children) + private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { var function = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); if (function is not null) { - var method = CreateSerializerMethodForComposedType(codeInterface, function); + var method = CreateSerializerMethodForComposedType(codeInterface, function, composedType); var serializerFunction = new CodeFunction(method) { Name = method.Name }; serializerFunction.AddUsing(function.Usings.ToArray()); @@ -363,11 +363,12 @@ private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeI return method; } - private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) + private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function, CodeComposedTypeBase composedType) { var method = CreateCodeMethod(codeInterface, function); // Add the key parameter if the composed type is a union of primitive values - method.AddParameter(CreateKeyParameter()); + if (ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + method.AddParameter(CreateKeyParameter()); method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); return method; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index e12fead36c..827d0937af 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -105,8 +105,7 @@ private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFun { writer.StartBlock($"case \"{mappedType.Key}\":"); var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); - var serializationName = GetSerializationMethodName(mappedType.Value, codeElement.OriginalLocalMethod); - writer.WriteLine($"writer.{serializationName}<{mappedTypeName}>(key, {paramName}, {GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)});"); + writer.WriteLine($"{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)}(writer, {paramName});"); writer.WriteLine("break;"); writer.DecreaseIndent(); } @@ -341,39 +340,24 @@ public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeM ArgumentNullException.ThrowIfNull(method); var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); var composedType = GetOriginalComposedType(propertyType); - var currentType = propertyType as CodeType; + CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; - return (composedType, currentType, propertyTypeName) switch + return (currentType, propertyTypeName) switch { - (CodeComposedTypeBase type, _, _) => GetComposedTypeSerializationMethodName(type, method), - (_, CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, - (_, _, "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration") => $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value", + (CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, + (_, "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration") => $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value", _ => "writeObjectValue" }; } - private static string GetComposedTypeSerializationMethodName(CodeComposedTypeBase composedType, CodeMethod method) + private static CodeType GetCodeTypeForComposedType(CodeComposedTypeBase composedType) { - ArgumentNullException.ThrowIfNull(method); ArgumentNullException.ThrowIfNull(composedType); - /* - Serialization will be delegated into a generated method because we cant tell in advance, Using the Pets example, whether its a Cat or Dog - without inspecting the discriminator property which only the generator can tell, so instead of writer.writeObjectValue("pet", responseObject.pet, serializePet) - * it will be something like: - - export function serializePet(writer: SerializationWriter, key: string, pet: Partial | undefined = {}) : void { - if (pet == undefined) return; - switch (pet.pet_type) { - case "Cat": - writer.writeObjectValue(key, pet, serializeCat); - break; - case "Dog": - writer.writeObjectValue(key, pet, serializeDog); - break; - } - } - */ - return $"serialize{ConventionServiceInstance.GetTypeString(composedType, method)}"; + return new CodeType + { + Name = composedType.Name, + TypeDefinition = composedType + }; } private static string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) From 2f2d8552279aa1e8395d9481a7cb7c084297b361 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 29 May 2024 16:34:29 +0300 Subject: [PATCH 025/117] **allow primitive intersection types as per existing yml test file --- .../Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 18 ++++++++++++------ .../TypeScript/TypeScriptConventionService.cs | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 3db5738427..fa3214137f 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -358,7 +358,7 @@ private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface co private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) { var method = CreateCodeMethod(codeInterface, function); - if (composedType is CodeUnionType && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) method.ReturnType = composedType; return method; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 827d0937af..36a665983d 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -182,9 +182,9 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, { var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); var composedType = GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType); - if (composedType is CodeUnionType codeUnion && ConventionServiceInstance.IsComposedOfPrimitives(codeUnion)) + if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { - WriteFactoryMethodBodyForUnionOfPrimitives(codeUnion, codeElement, writer, parseNodeParameter); + WriteFactoryMethodBodyForUnionOfPrimitives(composedType, codeElement, writer, parseNodeParameter); return; } @@ -199,8 +199,11 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, return; } - // It's a composed type but there isn't a discriminator property - writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + if (composedType is CodeUnionType) + { + // It's a composed type but there isn't a discriminator property + writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + } } private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) @@ -338,10 +341,13 @@ public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeM { ArgumentNullException.ThrowIfNull(propertyType); ArgumentNullException.ThrowIfNull(method); - var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); var composedType = GetOriginalComposedType(propertyType); - CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; + if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + return $"serialize{ConventionServiceInstance.GetTypeString(composedType, method)}"; + + var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); + CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; return (currentType, propertyTypeName) switch { (CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index ff90df6aa8..f874cf7154 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -65,6 +65,7 @@ public override string GetAccessModifier(AccessModifier access) public bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) { + // Primitive values don't have a discriminator property so it should be handled differently if any of the values is primitive return composedType?.Types.All(x => IsPrimitiveType(GetTypeString(x, composedType))) ?? false; } From 615b3fa6f884b24e0352da831229b742f1cc26fd Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 29 May 2024 22:57:57 +0300 Subject: [PATCH 026/117] typescript: add support for intersection type objects --- .../Refiners/TypeScriptRefiner.cs | 26 ++++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 82 +++++++++++++++---- .../CodeIntersectionTypeSampleYml.cs | 45 ++++++++++ .../TypeScript/CodeFunctionWriterTests.cs | 76 +++++++++++++++++ 4 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index fa3214137f..2596bc096b 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -303,7 +303,7 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, children); + ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); return children; } @@ -339,14 +339,23 @@ private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInt } } - private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, List children) + private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); - if (deserializerMethod is not null) + + if (deserializerMethod is null) return; + + // For code union Deserializer is not required, however its needed fo Intersection types + if (composedType is CodeIntersectionType) { - children.Remove(deserializerMethod); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); + var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); + var deserializerFunction = new CodeFunction(method) { Name = method.Name }; + deserializerFunction.AddUsing(deserializerMethod.Usings.ToArray()); + children.Add(deserializerFunction); } + + children.Remove(deserializerMethod); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); } private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction function) @@ -369,7 +378,12 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co // Add the key parameter if the composed type is a union of primitive values if (ConventionServiceInstance.IsComposedOfPrimitives(composedType)) method.AddParameter(CreateKeyParameter()); - method.AddParameter(function.OriginalLocalMethod.Parameters.ToArray()); + return method; + } + + private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) + { + var method = CreateCodeMethod(codeInterface, function); return method; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 36a665983d..776c6bce14 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -55,6 +55,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w case CodeMethodKind.ComposedTypeSerializer: WriteComposedTypeSerializer(codeElement, writer); break; + case CodeMethodKind.ComposedTypeDeserializer: + WriteComposedTypeDeserializer(codeElement, writer); + break; default: throw new InvalidOperationException("Invalid code method kind"); } } @@ -78,6 +81,22 @@ private static void WriteFactoryMethodBodyForUnionOfPrimitives(CodeComposedTypeB writer.CloseBlock(); } + private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer) + { + var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); + if (composedParam == null) return; + + if (GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; + + writer.StartBlock($"return {{"); + foreach (var mappedType in composedType.Types.ToArray()) + { + var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); + writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()}),"); + } + writer.CloseBlock(); + } + private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer) { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); @@ -91,9 +110,24 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite return; } + if (composedType is CodeIntersectionType) + { + WriteComposedTypeSerializationForCodeIntersectionType(composedType, composedParam, codeElement, writer); + return; + } + WriteComposedTypeSerialization(composedParam, codeElement, writer); } + private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) + { + foreach (var mappedType in composedType.Types.ToArray()) + { + var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); + writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()});"); + } + } + private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); @@ -182,28 +216,44 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, { var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); var composedType = GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType); - if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) - { - WriteFactoryMethodBodyForUnionOfPrimitives(composedType, codeElement, writer, parseNodeParameter); - return; - } - if (ShouldWriteDiscriminatorInformation(codeElement, composedType) && parseNodeParameter != null) + switch (composedType) { - WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); + case CodeComposedTypeBase type when ConventionServiceInstance.IsComposedOfPrimitives(type): + WriteFactoryMethodBodyForUnionOfPrimitives(type, codeElement, writer, parseNodeParameter); + break; + case CodeUnionType _ when parseNodeParameter != null: + WriteFactoryMethodBodyForCodeUnionType(codeElement, writer, parseNodeParameter); + break; + case CodeIntersectionType _ when parseNodeParameter != null: + WriteFactoryMethodBodyForCodeIntersectionType(codeElement, returnType, writer, parseNodeParameter); + break; + default: + WriteNormalFactoryMethodBody(codeElement, returnType, writer); + break; } + } - if (composedType is null) - { - WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); - return; - } + private void WriteFactoryMethodBodyForCodeIntersectionType(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter parseNodeParameter) + { + WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); + } + + private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, LanguageWriter writer, CodeParameter parseNodeParameter) + { + WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); + // It's a composed type but there isn't a discriminator property + writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + } - if (composedType is CodeUnionType) + private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) + { + var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); + if (ShouldWriteDiscriminatorInformation(codeElement, null) && parseNodeParameter != null) { - // It's a composed type but there isn't a discriminator property - writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); } + WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); } private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) @@ -263,7 +313,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM { codeFunction = parentNamespace?.FindChildByName(functionName); parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); - } while (codeFunction?.Name != functionName && parentNamespace is not null); + } while (!functionName.Equals(codeFunction?.Name, StringComparison.Ordinal) && parentNamespace is not null); return codeFunction; } diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs new file mode 100644 index 0000000000..7894c6155b --- /dev/null +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs @@ -0,0 +1,45 @@ +namespace Kiota.Builder.Tests.OpenApiSampleFiles; + +public static class CodeIntersectionTypeSampleYml +{ + public static readonly string OpenApiYaml = @" +openapi: 3.0.3 +info: + title: FooBar API + description: A sample API that returns an object FooBar which is an intersection of Foo and Bar. + version: 1.0.0 +servers: + - url: https://api.example.com/v1 +paths: + /foobar: + get: + summary: Get a FooBar object + description: Returns an object that is an intersection of Foo and Bar. + responses: + '200': + description: A FooBar object + content: + application/json: + schema: + $ref: '#/components/schemas/FooBar' +components: + schemas: + Foo: + type: object + properties: + foo: + type: string + required: + - foo + Bar: + type: object + properties: + bar: + type: string + required: + - bar + FooBar: + anyOf: + - $ref: '#/components/schemas/Foo' + - $ref: '#/components/schemas/Bar'"; +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 29c9ffc470..860afa0bdb 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1274,5 +1274,81 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() Assert.Contains("break", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } + + [Fact] + public async Task Writes_CodeIntersectionType_DeserializerFunctions() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("FooBar", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("foobarRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Deserializer function + var deserializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeDeserializer).FirstOrDefault(); + Assert.True(deserializerFunction is not null); + writer.Write(deserializerFunction); + var serializerFunctionStr = tw.ToString(); + Assert.Contains("...deserializeIntoBar(fooBar),", serializerFunctionStr); + Assert.Contains("...deserializeIntoFoo(fooBar),", serializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } + + [Fact] + public async Task Writes_CodeIntersectionType_SerializerFunctions() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("FooBar", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("foobarRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeSerializer).FirstOrDefault(); + Assert.True(serializerFunction is not null); + writer.Write(serializerFunction); + var serializerFunctionStr = tw.ToString(); + Assert.Contains("serializeBar(writer, fooBar);", serializerFunctionStr); + Assert.Contains("serializeFoo(writer, fooBar);", serializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } } From ae857c5971b8ba30114b916c032027e7a4f7c354 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 30 May 2024 13:27:28 +0300 Subject: [PATCH 027/117] fix primitive intersection bug --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 4 ++-- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 2596bc096b..69b98f6a71 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -345,8 +345,8 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI if (deserializerMethod is null) return; - // For code union Deserializer is not required, however its needed fo Intersection types - if (composedType is CodeIntersectionType) + // For code union Deserializer is not required, however its needed for Object Intersection types + if (composedType is CodeIntersectionType && !ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); var deserializerFunction = new CodeFunction(method) { Name = method.Name }; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 860afa0bdb..cc12d53909 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1159,8 +1159,6 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() Assert.NotNull(modelCodeFile); /* - * - * \/** * Creates a new instance of the appropriate class based on discriminator value * @returns {ValidationError_errors_value} @@ -1200,7 +1198,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1241,7 +1239,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); From 6084d07b9749ef4e4e075dc6e449b9159479b6d0 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 5 Jun 2024 20:13:24 +0300 Subject: [PATCH 028/117] refactor the factory method for primitive composed type values --- .../TypeScript/CodeComposedTypeBaseWriter.cs | 1 - .../Writers/TypeScript/CodeFunctionWriter.cs | 23 +++++++----------- .../TypeScript/CodeFunctionWriterTests.cs | 24 +++++++------------ 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs index 39b5c8748a..e0dc4864e2 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -25,7 +25,6 @@ public override void WriteCodeElement(TCodeComposedTypeBase codeElement, Languag var codeUnionString = string.Join($" {TypesDelimiter} ", codeElement.Types.Select(x => conventions.GetTypeString(x, codeElement))); - // TODO: documentation info writer.WriteLine($"export type {codeElement.Name.ToFirstCharacterUpperCase()} = {codeUnionString};"); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 776c6bce14..a6fe239ce7 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -62,23 +62,16 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } - private static void WriteFactoryMethodBodyForUnionOfPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) + private static void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) { ArgumentNullException.ThrowIfNull(parseNodeParameter); - writer.WriteLine($"const nodeValue = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}?.getNodeValue();"); - writer.StartBlock($"switch (typeof nodeValue) {{"); - foreach (var type in composedType.Types) - { - var nodeType = ConventionServiceInstance.GetTypeString(type, codeElement, false); - writer.WriteLine($"case \"{nodeType}\":"); - } - writer.IncreaseIndent(); - writer.WriteLine($"return nodeValue;"); - writer.DecreaseIndent(); - writer.StartBlock($"default:"); - writer.WriteLine($"return undefined;"); - writer.DecreaseIndent(); + var parseNodeParameterName = parseNodeParameter.Name.ToFirstCharacterLowerCase(); + writer.StartBlock($"if ({parseNodeParameterName}) {{"); + + string getPrimitiveValueString = string.Join($" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + writer.WriteLine($"return {getPrimitiveValueString};"); writer.CloseBlock(); + writer.WriteLine($"return undefined;"); } private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer) @@ -220,7 +213,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { case CodeComposedTypeBase type when ConventionServiceInstance.IsComposedOfPrimitives(type): - WriteFactoryMethodBodyForUnionOfPrimitives(type, codeElement, writer, parseNodeParameter); + WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: WriteFactoryMethodBodyForCodeUnionType(codeElement, writer, parseNodeParameter); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index cc12d53909..564bf124b2 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1163,15 +1163,12 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() * Creates a new instance of the appropriate class based on discriminator value * @returns {ValidationError_errors_value} *\/ - export function createValidationError_errors_valueFromDiscriminatorValue(node: ParseNode | undefined) : ValidationError_errors_value | undefined { - const nodeValue = node?.getNodeValue(); - switch (typeof nodeValue) { - case "number": - case "string": - return nodeValue; - default: - return undefined; - } + export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | undefined) : Primitives | undefined { + if (parseNode) { + parseNode.getNumberValue() || parseNode.getStringValue(); + } + return undefined; + } */ @@ -1180,13 +1177,8 @@ export function createValidationError_errors_valueFromDiscriminatorValue(node: P Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); - Assert.Contains("return", result); - Assert.Contains("const", result); - Assert.Contains("switch (typeof", result); - Assert.Contains("case \"number\":", result); - Assert.Contains("case \"string\":", result); - Assert.Contains("return nodeValue;", result); - Assert.Contains("default", result); + Assert.Contains("if (parseNode) {", result); + Assert.Contains("return parseNode.getNumberValue() || parseNode.getStringValue();", result); Assert.Contains("return undefined;", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } From 35b6120a1ebdd7da669339669bdb90ffc6b30fce Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 7 Jun 2024 11:39:44 +0300 Subject: [PATCH 029/117] removed unused import statement --- .../Refiners/TypeScriptRefiner.cs | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 69b98f6a71..ab4744827f 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -308,40 +308,59 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac return children; } + private static CodeFunction? FindFunctionOfKind(List elements, CodeMethodKind kind) + { + return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind == kind); + } + + private static void RemoveOldCodeFunctionAndAddNewOne(List children, CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction oldCodeFunction, CodeFunction newCodeFunction) + { + children.Remove(oldCodeFunction); + RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, oldCodeFunction); + children.Add(newCodeFunction); + } + + private static void RemoveUnusedDeserializerImport(List children, CodeFunction factoryFunction) + { + var deserializerMethod = FindFunctionOfKind(children, CodeMethodKind.Deserializer); + if (deserializerMethod is not null) + factoryFunction.RemoveUsingsByDeclarationName(deserializerMethod.Name); + } + private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var factoryMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Factory); + var factoryMethod = FindFunctionOfKind(children, CodeMethodKind.Factory); - if (factoryMethod is not null) - { - var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); - var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; - factoryFunction.AddUsing(factoryMethod.Usings.ToArray()); + if (factoryMethod is null) return; + + var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); + var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; + factoryFunction.AddUsing(factoryMethod.Usings.ToArray()); + + RemoveOldCodeFunctionAndAddNewOne(children, codeInterface, codeNamespace, factoryMethod, factoryFunction); - children.Remove(factoryMethod); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, factoryMethod); - children.Add(factoryFunction); + // Remove the deserializer import statement if its not being used + if (composedType is CodeUnionType || ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + { + RemoveUnusedDeserializerImport(children, factoryFunction); } } private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var function = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Serializer); - if (function is not null) - { - var method = CreateSerializerMethodForComposedType(codeInterface, function, composedType); - var serializerFunction = new CodeFunction(method) { Name = method.Name }; - serializerFunction.AddUsing(function.Usings.ToArray()); + var function = FindFunctionOfKind(children, CodeMethodKind.Serializer); + if (function is null) return; - children.Remove(function); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, function); - children.Add(serializerFunction); - } + var method = CreateSerializerMethodForComposedType(codeInterface, function, composedType); + var serializerFunction = new CodeFunction(method) { Name = method.Name }; + serializerFunction.AddUsing(function.Usings.ToArray()); + + RemoveOldCodeFunctionAndAddNewOne(children, codeInterface, codeNamespace, function, serializerFunction); } private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var deserializerMethod = children.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer); + var deserializerMethod = FindFunctionOfKind(children, CodeMethodKind.Deserializer); if (deserializerMethod is null) return; From b5baad271f6174c835f5a3effadb57d312aa5d82 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 7 Jun 2024 16:34:25 +0300 Subject: [PATCH 030/117] inline composed types --- .../Writers/TypeScript/CodeFunctionWriter.cs | 6 +-- .../TypeScript/CodeIntersectionTypeWriter.cs | 6 +-- .../Writers/TypeScript/CodeMethodWriter.cs | 16 +++--- .../Writers/TypeScript/CodePropertyWriter.cs | 3 +- .../TypeScript/TypeScriptConventionService.cs | 53 ++++++++++++++++--- 5 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index a6fe239ce7..16d067399f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -30,7 +30,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var returnType = codeMethod.Kind is CodeMethodKind.Factory || (codeMethod.Kind is CodeMethodKind.ComposedTypeFactory && !isComposedOfPrimitives) ? factoryMethodReturnType : - conventions.GetTypeString(codeMethod.ReturnType, codeElement); + GetTypescriptTypeString(codeMethod.ReturnType, codeElement, inlineComposedTypeString: true); var isVoid = "void".EqualsIgnoreCase(returnType); CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.OriginalLocalMethod, writer, isVoid, conventions); CodeMethodWriter.WriteMethodPrototypeInternal(codeElement.OriginalLocalMethod, writer, returnType, isVoid, conventions, true); @@ -355,7 +355,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro var isCollectionOfEnum = IsCodePropertyCollectionOfEnum(codeProperty); var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); - var propTypeName = conventions.GetTypeString(codeProperty.Type, codeProperty.Parent!, false); + var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); var serializationName = GetSerializationMethodName(codeProperty.Type, codeFunction.OriginalLocalMethod); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; @@ -387,7 +387,7 @@ public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeM var composedType = GetOriginalComposedType(propertyType); if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) - return $"serialize{ConventionServiceInstance.GetTypeString(composedType, method)}"; + return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs index d5c6f9d59a..a3a949712c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -2,12 +2,8 @@ namespace Kiota.Builder.Writers.TypeScript; -public class CodeIntersectionTypeWriter : CodeComposedTypeBaseWriter +public class CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) { - public CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } - public override string TypesDelimiter { get => "&"; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 2e9a99cc3b..034fef66bb 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -4,13 +4,11 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; using Kiota.Builder.OrderComparers; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; -public class CodeMethodWriter : BaseElementWriter +public class CodeMethodWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) { - public CodeMethodWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); @@ -18,7 +16,7 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri ArgumentNullException.ThrowIfNull(writer); if (codeElement.Parent is CodeFunction) return; - var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); + var returnType = GetTypescriptTypeString(codeElement.ReturnType, codeElement, inlineComposedTypeString: true); var isVoid = "void".EqualsIgnoreCase(returnType); WriteMethodDocumentation(codeElement, writer, isVoid); WriteMethodPrototype(codeElement, writer, returnType, isVoid); @@ -35,15 +33,15 @@ internal static void WriteMethodDocumentationInternal(CodeMethod code, LanguageW var returnRemark = (isVoid, code.IsAsync) switch { (true, _) => string.Empty, - (false, true) => $"@returns {{Promise<{typeScriptConventionService.GetTypeString(code.ReturnType, code)}>}}", - (false, false) => $"@returns {{{typeScriptConventionService.GetTypeString(code.ReturnType, code)}}}", + (false, true) => $"@returns {{Promise<{GetTypescriptTypeString(code.ReturnType, code, inlineComposedTypeString: true)}>}}", + (false, false) => $"@returns {{{GetTypescriptTypeString(code.ReturnType, code, inlineComposedTypeString: true)}}}", }; typeScriptConventionService.WriteLongDescription(code, writer, code.Parameters .Where(static x => x.Documentation.DescriptionAvailable) .OrderBy(static x => x.Name) - .Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => typeScriptConventionService.GetTypeString(type, code), TypeScriptConventionService.ReferenceTypePrefix, TypeScriptConventionService.ReferenceTypeSuffix, TypeScriptConventionService.RemoveInvalidDescriptionCharacters)}") + .Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => GetTypescriptTypeString(type, code, inlineComposedTypeString: true), TypeScriptConventionService.ReferenceTypePrefix, TypeScriptConventionService.ReferenceTypeSuffix, TypeScriptConventionService.RemoveInvalidDescriptionCharacters)}") .Union([returnRemark]) .Union(GetThrownExceptionsRemarks(code, typeScriptConventionService))); } @@ -57,7 +55,7 @@ private static IEnumerable GetThrownExceptionsRemarks(CodeMethod code, T "XXX" => "4XX or 5XX", _ => errorMapping.Key, }; - var errorTypeString = typeScriptConventionService.GetTypeString(errorMapping.Value, code, false); + var errorTypeString = GetTypescriptTypeString(errorMapping.Value, code, false, inlineComposedTypeString: true); yield return $"@throws {{{errorTypeString}}} error when the service returns a {statusCode} status code"; } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index bd5ee91049..b1cd92168e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -1,6 +1,7 @@ using System; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; public class CodePropertyWriter : BaseElementWriter @@ -12,7 +13,7 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w ArgumentNullException.ThrowIfNull(writer); if (codeElement.ExistsInExternalBaseType) return; - var returnType = conventions.GetTypeString(codeElement.Type, codeElement); + var returnType = GetTypescriptTypeString(codeElement.Type, codeElement, inlineComposedTypeString: true); var isFlagEnum = codeElement.Type is CodeType { TypeDefinition: CodeEnum { Flags: true } } && !codeElement.Type.IsCollection;//collection of flagged enums are not supported/don't make sense diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index f874cf7154..6f32a8fb8d 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -72,7 +72,7 @@ public bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) { ArgumentNullException.ThrowIfNull(parameter); - var paramType = GetTypeString(parameter.Type, targetElement); + var paramType = GetTypescriptTypeString(parameter.Type, targetElement, inlineComposedTypeString: true); var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { @@ -87,28 +87,67 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen }; return $"{parameter.Name.ToFirstCharacterLowerCase()}{(parameter.Optional && parameter.Type.IsNullable ? "?" : string.Empty)}: {partialPrefix}{paramType}{partialSuffix}{(parameter.Type.IsNullable ? " | undefined" : string.Empty)}{defaultValueSuffix}"; } + public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null) + { + return GetTypescriptTypeString(code, targetElement, includeCollectionInformation, writer); + } + + public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null, bool inlineComposedTypeString = false) { ArgumentNullException.ThrowIfNull(code); ArgumentNullException.ThrowIfNull(targetElement); var collectionSuffix = code.CollectionKind == CodeTypeCollectionKind.None || !includeCollectionInformation ? string.Empty : "[]"; - CodeTypeBase codeType = GetOriginalComposedType(code) is CodeComposedTypeBase composedType ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : code; + var composedType = GetOriginalComposedType(code); - if (codeType is not CodeType currentType) + if (inlineComposedTypeString && composedType?.Types.Any() == true) + { + return GetComposedTypeTypeString(composedType, targetElement, collectionSuffix); + } + + CodeTypeBase codeType = composedType is not null ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : code; + + if (!(codeType is CodeType currentType)) { throw new InvalidOperationException($"type of type {code.GetType()} is unknown"); } - var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias : TranslateType(currentType); - var genericParameters = currentType.GenericTypeParameterValues.Count != 0 ? - $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypeString(x, targetElement, includeCollectionInformation)))}>" : - string.Empty; + var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) + ? alias + : ConventionServiceInstance.TranslateType(currentType); + + var genericParameters = currentType.GenericTypeParameterValues.Count != 0 + ? $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation)))}>" + : string.Empty; return $"{typeName}{collectionSuffix}{genericParameters}"; } + /** + * Gets the composed type string representation e.g `type1 | type2 | type3[]` or `(type1 & type2 & type3)[]` + * @param composedType The composed type to get the string representation for + * @param targetElement The target element + * @returns The composed type string representation + */ + private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix) + { + if (!composedType.Types.Any()) throw new InvalidOperationException($"Composed type should be comprised of at least one type"); + var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement))); + return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; + } + + private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) + { + return codeComposedTypeBase switch + { + CodeUnionType _ => " | ", + CodeIntersectionType _ => " & ", + _ => throw new InvalidOperationException("unknown composed type"), + }; + } + private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { if (targetElement.GetImmediateParentOfType() is IBlock parentBlock && From 37e78fa41acbcfc9c21a923de2f072c670b5a5c8 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 7 Jun 2024 16:48:18 +0300 Subject: [PATCH 031/117] remove unused param --- src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 034fef66bb..04592b931f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -41,11 +41,11 @@ internal static void WriteMethodDocumentationInternal(CodeMethod code, LanguageW code.Parameters .Where(static x => x.Documentation.DescriptionAvailable) .OrderBy(static x => x.Name) - .Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => GetTypescriptTypeString(type, code, inlineComposedTypeString: true), TypeScriptConventionService.ReferenceTypePrefix, TypeScriptConventionService.ReferenceTypeSuffix, TypeScriptConventionService.RemoveInvalidDescriptionCharacters)}") + .Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => GetTypescriptTypeString(type, code, inlineComposedTypeString: true), ReferenceTypePrefix, ReferenceTypeSuffix, RemoveInvalidDescriptionCharacters)}") .Union([returnRemark]) - .Union(GetThrownExceptionsRemarks(code, typeScriptConventionService))); + .Union(GetThrownExceptionsRemarks(code))); } - private static IEnumerable GetThrownExceptionsRemarks(CodeMethod code, TypeScriptConventionService typeScriptConventionService) + private static IEnumerable GetThrownExceptionsRemarks(CodeMethod code) { if (code.Kind is not CodeMethodKind.RequestExecutor) yield break; foreach (var errorMapping in code.ErrorMappings) From 5688854c572ced06c1a482d66db6acd46b6e8628 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 7 Jun 2024 21:06:42 +0300 Subject: [PATCH 032/117] fix sonar warnings --- .../TypeScript/TypeScriptConventionService.cs | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 6f32a8fb8d..cbf3cf0210 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -21,6 +21,34 @@ public static TypeScriptConventionService ConventionServiceInstance return conventionService; } } + + #pragma warning disable CA1707 // Remove the underscores + public const string TYPE_INTEGER = "integer"; + public const string TYPE_INT64 = "int64"; + public const string TYPE_FLOAT = "float"; + public const string TYPE_DOUBLE = "double"; + public const string TYPE_BYTE = "byte"; + public const string TYPE_SBYTE = "sbyte"; + public const string TYPE_DECIMAL = "decimal"; + public const string TYPE_BINARY = "binary"; + public const string TYPE_BASE64 = "base64"; + public const string TYPE_BASE64URL = "base64url"; + public const string TYPE_GUID = "Guid"; + public const string TYPE_STRING = "String"; + public const string TYPE_OBJECT = "Object"; + public const string TYPE_BOOLEAN = "Boolean"; + public const string TYPE_VOID = "Void"; + public const string TYPE_LOWERCASE_STRING = "string"; + public const string TYPE_LOWERCASE_OBJECT = "object"; + public const string TYPE_LOWERCASE_BOOLEAN = "boolean"; + public const string TYPE_LOWERCASE_VOID = "void"; + public const string TYPE_BYTE_ARRAY = "byte[]"; + public const string TYPE_NUMBER = "number"; + public const string TYPE_DATE = "Date"; + public const string TYPE_DATE_ONLY = "DateOnly"; + public const string TYPE_TIME_ONLY = "TimeOnly"; + public const string TYPE_DURATION = "Duration"; + #pragma warning restore CA1707 // Remove the underscores internal void WriteAutoGeneratedStart(LanguageWriter writer) { @@ -170,12 +198,12 @@ public override string TranslateType(CodeType type) { return type?.Name switch { - "integer" or "int64" or "float" or "double" or "byte" or "sbyte" or "decimal" => "number", - "binary" or "base64" or "base64url" => "string", - "Guid" => "Guid", - "String" or "Object" or "Boolean" or "Void" or "string" or "object" or "boolean" or "void" => type.Name.ToFirstCharacterLowerCase(), // little casing hack - null => "object", - _ => GetCodeTypeName(type) is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : "object", + TYPE_INTEGER or TYPE_INT64 or TYPE_FLOAT or TYPE_DOUBLE or TYPE_BYTE or TYPE_SBYTE or TYPE_DECIMAL => TYPE_NUMBER, + TYPE_BINARY or TYPE_BASE64 or TYPE_BASE64URL => TYPE_STRING, + TYPE_GUID => TYPE_GUID, + TYPE_STRING or TYPE_OBJECT or TYPE_BOOLEAN or TYPE_VOID or TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_OBJECT or TYPE_LOWERCASE_BOOLEAN or TYPE_LOWERCASE_VOID => type.Name.ToFirstCharacterLowerCase(), + null => TYPE_OBJECT, + _ => GetCodeTypeName(type) is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : TYPE_OBJECT, }; } @@ -193,7 +221,11 @@ public static bool IsPrimitiveType(string typeName) { return typeName switch { - "number" or "string" or "byte[]" or "boolean" or "void" => true, + TYPE_NUMBER or + TYPE_LOWERCASE_STRING or + TYPE_BYTE_ARRAY or + TYPE_LOWERCASE_BOOLEAN or + TYPE_LOWERCASE_VOID => true, _ => false, }; } @@ -314,9 +346,8 @@ private static string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeB { return propertyTypeName switch { - "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration" => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", + TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_BOOLEAN or TYPE_NUMBER or TYPE_GUID or TYPE_DATE or TYPE_DATE_ONLY or TYPE_TIME_ONLY or TYPE_DURATION => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, method)})" }; } - } From d101bf9ac66cbaa7ddb5453661917f039f635a6b Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 7 Jun 2024 21:11:46 +0300 Subject: [PATCH 033/117] format code --- .../TypeScript/TypeScriptConventionService.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index cbf3cf0210..d45c4ea110 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -21,8 +21,8 @@ public static TypeScriptConventionService ConventionServiceInstance return conventionService; } } - - #pragma warning disable CA1707 // Remove the underscores + +#pragma warning disable CA1707 // Remove the underscores public const string TYPE_INTEGER = "integer"; public const string TYPE_INT64 = "int64"; public const string TYPE_FLOAT = "float"; @@ -48,7 +48,7 @@ public static TypeScriptConventionService ConventionServiceInstance public const string TYPE_DATE_ONLY = "DateOnly"; public const string TYPE_TIME_ONLY = "TimeOnly"; public const string TYPE_DURATION = "Duration"; - #pragma warning restore CA1707 // Remove the underscores +#pragma warning restore CA1707 // Remove the underscores internal void WriteAutoGeneratedStart(LanguageWriter writer) { @@ -221,10 +221,10 @@ public static bool IsPrimitiveType(string typeName) { return typeName switch { - TYPE_NUMBER or - TYPE_LOWERCASE_STRING or - TYPE_BYTE_ARRAY or - TYPE_LOWERCASE_BOOLEAN or + TYPE_NUMBER or + TYPE_LOWERCASE_STRING or + TYPE_BYTE_ARRAY or + TYPE_LOWERCASE_BOOLEAN or TYPE_LOWERCASE_VOID => true, _ => false, }; From f71be978e1272a809b6d8b1292760dc5c185c165 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 10 Jun 2024 14:02:48 +0300 Subject: [PATCH 034/117] address pr comments --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 4 +--- .../Writers/TypeScript/CodeComposedTypeBaseWriter.cs | 5 +---- .../Writers/TypeScript/CodeConstantWriter.cs | 4 ++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 6 ++---- .../Writers/TypeScript/CodeUnionTypeWriter.cs | 6 +----- .../Writers/TypeScript/TypeScriptConventionService.cs | 8 ++++---- 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index ab4744827f..3756347e9e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -6,8 +6,6 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; -using Kiota.Builder.Writers.TypeScript; -using static Kiota.Builder.Writers.TypeScript.CodeFunctionWriter; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Refiners; @@ -1028,7 +1026,7 @@ private static void RenameCodeInterfaceParamsInSerializers(CodeFunction codeFunc private static string GetFinalInterfaceName(CodeInterface codeInterface) { - return codeInterface.OriginalClass?.Name.ToFirstCharacterUpperCase() ?? throw new InvalidOperationException($"The refiner was unable to find the original class for {codeInterface.Name}"); + return codeInterface.OriginalClass.Name.ToFirstCharacterUpperCase(); } private static void GenerateModelInterfaces(CodeElement currentElement, Func interfaceNamingCallback) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs index e0dc4864e2..deb44c235f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -5,11 +5,8 @@ namespace Kiota.Builder.Writers.TypeScript; -public abstract class CodeComposedTypeBaseWriter : BaseElementWriter where TCodeComposedTypeBase : CodeComposedTypeBase where TConventionsService : TypeScriptConventionService +public abstract class CodeComposedTypeBaseWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) where TCodeComposedTypeBase : CodeComposedTypeBase { - protected CodeComposedTypeBaseWriter(TConventionsService conventionService) : base(conventionService) - { - } public abstract string TypesDelimiter { get; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index d70d1d1147..61ae4912a9 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -102,7 +102,7 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var isEnum = executorMethod.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeEnum; var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); - var isPrimitive = IsPrimitiveType(returnTypeWithoutCollectionSymbol); + var isPrimitive = ConventionServiceInstance.IsPrimitiveType(returnTypeWithoutCollectionSymbol); writer.StartBlock($"{executorMethod.Name.ToFirstCharacterLowerCase()}: {{"); var urlTemplateValue = executorMethod.HasUrlTemplateOverride ? $"\"{executorMethod.UrlTemplateOverride}\"" : uriTemplateConstant.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"uriTemplate: {urlTemplateValue},"); @@ -168,7 +168,7 @@ private string GetTypeFactory(bool isVoid, bool isStream, CodeMethod codeElement { if (isVoid) return string.Empty; var typeName = conventions.TranslateType(codeElement.ReturnType); - if (isStream || IsPrimitiveType(typeName)) return $" \"{typeName}\""; + if (isStream || ConventionServiceInstance.IsPrimitiveType(typeName)) return $" \"{typeName}\""; return $" {GetFactoryMethodName(codeElement.ReturnType, codeElement, writer)}"; } private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 16d067399f..b02b6ddd6f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -77,9 +77,8 @@ private static void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase com private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer) { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam == null) return; - if (GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; writer.StartBlock($"return {{"); foreach (var mappedType in composedType.Types.ToArray()) @@ -93,9 +92,8 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer) { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam == null) return; - if (GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; if (conventions.IsComposedOfPrimitives(composedType)) { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs index 2c4e42bc6c..00d7b06a7e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs @@ -2,12 +2,8 @@ namespace Kiota.Builder.Writers.TypeScript; -public class CodeUnionTypeWriter : CodeComposedTypeBaseWriter +public class CodeUnionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) { - public CodeUnionTypeWriter(TypeScriptConventionService conventionService) : base(conventionService) - { - } - public override string TypesDelimiter { get => "|"; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index d45c4ea110..6a767a6f64 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -137,7 +137,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ CodeTypeBase codeType = composedType is not null ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : code; - if (!(codeType is CodeType currentType)) + if (codeType is not CodeType currentType) { throw new InvalidOperationException($"type of type {code.GetType()} is unknown"); } @@ -216,8 +216,8 @@ private static string GetCodeTypeName(CodeType codeType) return (!string.IsNullOrEmpty(codeType.TypeDefinition?.Name) ? codeType.TypeDefinition.Name : codeType.Name).ToFirstCharacterUpperCase(); } -#pragma warning disable CA1822 // Method should be static - public static bool IsPrimitiveType(string typeName) + + public bool IsPrimitiveType(string typeName) { return typeName switch { @@ -229,7 +229,7 @@ TYPE_LOWERCASE_BOOLEAN or _ => false, }; } -#pragma warning restore CA1822 // Method should be static + internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription?.Replace("\\", "/", StringComparison.OrdinalIgnoreCase) ?? string.Empty; public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") { From 84b8792c5b34e529e4d04deca04d23d87280b3e3 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 11:51:49 +0300 Subject: [PATCH 035/117] adress pr comments --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 10 ++-------- .../Writers/TypeScript/CodeComposedTypeBaseWriter.cs | 2 +- .../Writers/TypeScript/CodeIntersectionTypeWriter.cs | 2 +- .../Writers/TypeScript/CodeUnionTypeWriter.cs | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 3756347e9e..d3b693d5c4 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -308,7 +308,7 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac private static CodeFunction? FindFunctionOfKind(List elements, CodeMethodKind kind) { - return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind == kind); + return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.IsOfKind(kind)); } private static void RemoveOldCodeFunctionAndAddNewOne(List children, CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction oldCodeFunction, CodeFunction newCodeFunction) @@ -365,7 +365,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI // For code union Deserializer is not required, however its needed for Object Intersection types if (composedType is CodeIntersectionType && !ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { - var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); + var method = CreateCodeMethod(codeInterface, deserializerMethod); var deserializerFunction = new CodeFunction(method) { Name = method.Name }; deserializerFunction.AddUsing(deserializerMethod.Usings.ToArray()); children.Add(deserializerFunction); @@ -398,12 +398,6 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co return method; } - private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) - { - var method = CreateCodeMethod(codeInterface, function); - return method; - } - private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeFunction function) { var method = new CodeMethod diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs index deb44c235f..5dc9ea85f0 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeComposedTypeBaseWriter.cs @@ -5,7 +5,7 @@ namespace Kiota.Builder.Writers.TypeScript; -public abstract class CodeComposedTypeBaseWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) where TCodeComposedTypeBase : CodeComposedTypeBase +public abstract class CodeComposedTypeBaseWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) where TCodeComposedTypeBase : CodeComposedTypeBase { public abstract string TypesDelimiter { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs index a3a949712c..be9519356c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -2,7 +2,7 @@ namespace Kiota.Builder.Writers.TypeScript; -public class CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) +public class CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) { public override string TypesDelimiter { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs index 00d7b06a7e..ce51f20892 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUnionTypeWriter.cs @@ -2,7 +2,7 @@ namespace Kiota.Builder.Writers.TypeScript; -public class CodeUnionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) +public class CodeUnionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) { public override string TypesDelimiter { From c247ea1994ba7672fd886293d079d4f292e74bf7 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 16:04:50 +0300 Subject: [PATCH 036/117] address pr comments --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 10 ++++++++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index d3b693d5c4..3756347e9e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -308,7 +308,7 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac private static CodeFunction? FindFunctionOfKind(List elements, CodeMethodKind kind) { - return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.IsOfKind(kind)); + return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind == kind); } private static void RemoveOldCodeFunctionAndAddNewOne(List children, CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction oldCodeFunction, CodeFunction newCodeFunction) @@ -365,7 +365,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI // For code union Deserializer is not required, however its needed for Object Intersection types if (composedType is CodeIntersectionType && !ConventionServiceInstance.IsComposedOfPrimitives(composedType)) { - var method = CreateCodeMethod(codeInterface, deserializerMethod); + var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); var deserializerFunction = new CodeFunction(method) { Name = method.Name }; deserializerFunction.AddUsing(deserializerMethod.Usings.ToArray()); children.Add(deserializerFunction); @@ -398,6 +398,12 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co return method; } + private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) + { + var method = CreateCodeMethod(codeInterface, function); + return method; + } + private static CodeMethod CreateCodeMethod(CodeInterface codeInterface, CodeFunction function) { var method = new CodeMethod diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index b02b6ddd6f..df289e80c7 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -68,10 +68,10 @@ private static void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase com var parseNodeParameterName = parseNodeParameter.Name.ToFirstCharacterLowerCase(); writer.StartBlock($"if ({parseNodeParameterName}) {{"); - string getPrimitiveValueString = string.Join($" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + string getPrimitiveValueString = string.Join(" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); writer.WriteLine($"return {getPrimitiveValueString};"); writer.CloseBlock(); - writer.WriteLine($"return undefined;"); + writer.WriteLine("return undefined;"); } private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer) @@ -80,7 +80,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri if (composedParam is null || GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; - writer.StartBlock($"return {{"); + writer.StartBlock("return {"); foreach (var mappedType in composedType.Types.ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); @@ -123,7 +123,7 @@ private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFun { var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} == undefined) return;"); + writer.WriteLine($"if ({paramName} === undefined) return;"); writer.StartBlock($"switch ({paramName}.{discriminatorPropertyName}) {{"); foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) @@ -156,7 +156,7 @@ private void WriteTypeSerialization(CodeTypeBase type, string paramName, CodeFun { var nodeType = conventions.GetTypeString(type, method, false); var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod); - if (serializationName == null || nodeType == null) return; + if (string.IsNullOrEmpty(serializationName) || string.IsNullOrEmpty(nodeType)) return; writer.StartBlock($"case \"{nodeType}\":"); writer.WriteLine($"writer.{serializationName}(key, {paramName});"); From 9cb0f8488d50c6bb252f939fb8e32b02240d3f6d Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 16:18:54 +0300 Subject: [PATCH 037/117] address pr comments --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index df289e80c7..56fafb8e44 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -78,7 +78,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam is null || GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not {} composedType) return; writer.StartBlock("return {"); foreach (var mappedType in composedType.Types.ToArray()) @@ -93,7 +93,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam is null || GetOriginalComposedType(composedParam) is not CodeComposedTypeBase composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not {} composedType) return; if (conventions.IsComposedOfPrimitives(composedType)) { @@ -141,7 +141,7 @@ private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFun private void WriteComposedTypeSerializationForPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} == undefined) return;"); + writer.WriteLine($"if ({paramName} === undefined) return;"); writer.StartBlock($"switch (typeof {paramName}) {{"); foreach (var type in composedType.Types) From dba8f83743a6216a945bef39db96485c52e07a57 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 16:31:28 +0300 Subject: [PATCH 038/117] address pr coments --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 56fafb8e44..45ab24ce14 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -254,7 +254,7 @@ private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnTy writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()}{parseNodeParameterForPrimitiveValues};"); } - private bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement, CodeComposedTypeBase? composedType) + private static bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement, CodeComposedTypeBase? composedType) { return codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType || composedType is CodeUnionType; } From 2966b661a945801f0a1b9a1fb4648c4fad103145 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 16:40:10 +0300 Subject: [PATCH 039/117] format code --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 45ab24ce14..30fd08f9c9 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -78,7 +78,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam is null || GetOriginalComposedType(composedParam) is not {} composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); foreach (var mappedType in composedType.Types.ToArray()) @@ -93,7 +93,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite { var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam is null || GetOriginalComposedType(composedParam) is not {} composedType) return; + if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; if (conventions.IsComposedOfPrimitives(composedType)) { From 8c5276799c4899d87e4adadcfa1a92eff8bf3889 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 16:50:29 +0300 Subject: [PATCH 040/117] address pr comments --- src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs | 4 ++-- .../Writers/TypeScript/TypeScriptConventionService.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 61ae4912a9..d70d1d1147 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -102,7 +102,7 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var isEnum = executorMethod.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeEnum; var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); - var isPrimitive = ConventionServiceInstance.IsPrimitiveType(returnTypeWithoutCollectionSymbol); + var isPrimitive = IsPrimitiveType(returnTypeWithoutCollectionSymbol); writer.StartBlock($"{executorMethod.Name.ToFirstCharacterLowerCase()}: {{"); var urlTemplateValue = executorMethod.HasUrlTemplateOverride ? $"\"{executorMethod.UrlTemplateOverride}\"" : uriTemplateConstant.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"uriTemplate: {urlTemplateValue},"); @@ -168,7 +168,7 @@ private string GetTypeFactory(bool isVoid, bool isStream, CodeMethod codeElement { if (isVoid) return string.Empty; var typeName = conventions.TranslateType(codeElement.ReturnType); - if (isStream || ConventionServiceInstance.IsPrimitiveType(typeName)) return $" \"{typeName}\""; + if (isStream || IsPrimitiveType(typeName)) return $" \"{typeName}\""; return $" {GetFactoryMethodName(codeElement.ReturnType, codeElement, writer)}"; } private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 6a767a6f64..72fc00d130 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -217,7 +217,7 @@ private static string GetCodeTypeName(CodeType codeType) return (!string.IsNullOrEmpty(codeType.TypeDefinition?.Name) ? codeType.TypeDefinition.Name : codeType.Name).ToFirstCharacterUpperCase(); } - public bool IsPrimitiveType(string typeName) + public static bool IsPrimitiveType(string typeName) { return typeName switch { From 1f82494fd59798a903cde42ad6d56321276c4216 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 17:58:59 +0300 Subject: [PATCH 041/117] increase test coverage --- .../TypeScript/CodeFunctionWriterTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 564bf124b2..d6de5d8897 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1265,6 +1265,51 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } + [Fact] + public async Task Writes_CodeIntersectionType_FactoryMethod() + { + var expectedFactoryFunctionString = @"/** + * Creates a new instance of the appropriate class based on discriminator value + * @param parseNode The parse node to use to read the discriminator value and create the object + * @returns {Bar & Foo} + */ +export function createFooBarFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record void>) { + return deserializeIntoFooBar; +"; + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("FooBar", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("foobarRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Factory Function + var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeFactory).FirstOrDefault(); + Assert.True(factoryFunction is not null); + writer.Write(factoryFunction); + var serializerFunctionStr = tw.ToString(); + Assert.Contains(expectedFactoryFunctionString, serializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } + [Fact] public async Task Writes_CodeIntersectionType_DeserializerFunctions() { From 16d848c625bac370b8a6ba9d4ce3879790225b12 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 12 Jun 2024 18:21:20 +0300 Subject: [PATCH 042/117] increase test coverage --- .../TypeScript/CodeFunctionWriterTests.cs | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index d6de5d8897..b266e8c62b 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1183,6 +1183,63 @@ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | un AssertExtensions.CurlyBracesAreClosed(result, 1); } + [Fact] + public async Task Writes_UnionOfObjects_FactoryMethod() + { + var expectedResultString = @"/** + * Creates a new instance of the appropriate class based on discriminator value + * @param parseNode The parse node to use to read the discriminator value and create the object + * @returns {Cat | Dog} + */ +export function createPetsPatchRequestBodyFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record void>) { + const mappingValueNode = parseNode.getChildNode(""pet_type""); + if (mappingValueNode) { + const mappingValue = mappingValueNode.getStringValue(); + if (mappingValue) { + switch (mappingValue) { + case ""Cat"": + return deserializeIntoCat; + case ""Dog"": + return deserializeIntoDog; + } + } + } + throw new Error(""A discriminator property is required to distinguish a union type""); +"; + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Pets", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pets"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("petsRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeFactory).FirstOrDefault(); + Assert.True(factoryFunction is not null); + writer.Write(factoryFunction); + var result = tw.ToString(); + Assert.Contains(expectedResultString, result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + [Fact] public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() { @@ -1305,9 +1362,9 @@ export function createFooBarFromDiscriminatorValue(parseNode: ParseNode | undefi var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeFactory).FirstOrDefault(); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); - var serializerFunctionStr = tw.ToString(); - Assert.Contains(expectedFactoryFunctionString, serializerFunctionStr); - AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + var result = tw.ToString(); + Assert.Contains(expectedFactoryFunctionString, result); + AssertExtensions.CurlyBracesAreClosed(result, 1); } [Fact] From c86251d80a4009ca2079706c70959cf5e91377f8 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 13 Jun 2024 11:46:15 +0300 Subject: [PATCH 043/117] address pr comments --- .../Refiners/TypeScriptRefiner.cs | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 3756347e9e..1a2bfbae7a 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -453,35 +453,14 @@ private static CodeParameter CreateKeyParameter() return element switch { CodeParameter param => GetOriginalComposedType(param.Type), - CodeType codeType => GetOriginalComposedType(codeType), - CodeClass codeClass => GetOriginalComposedType(codeClass), - CodeInterface codeInterface => GetOriginalComposedType(codeInterface), + CodeType codeType when codeType.TypeDefinition is not null => GetOriginalComposedType(codeType.TypeDefinition), + CodeClass codeClass => codeClass.OriginalComposedType, + CodeInterface codeInterface => codeInterface.OriginalClass.OriginalComposedType, CodeComposedTypeBase composedType => composedType, _ => null, }; } - public static CodeComposedTypeBase? GetOriginalComposedType(CodeType codeType) - { - return codeType?.TypeDefinition switch - { - CodeComposedTypeBase composedType => composedType, - CodeInterface ci => GetOriginalComposedType(ci), - CodeClass cc => GetOriginalComposedType(cc), - _ => null, - }; - } - - public static CodeComposedTypeBase? GetOriginalComposedType(CodeInterface codeInterface) - { - return codeInterface?.OriginalClass is CodeClass originalClass ? GetOriginalComposedType(originalClass) : null; - } - - public static CodeComposedTypeBase? GetOriginalComposedType(CodeClass codeClass) - { - return codeClass?.OriginalComposedType; - } - private static readonly CodeUsing[] navigationMetadataUsings = [ new CodeUsing { From 111b0e122d2d6575188e952b6274057822ea8c8e Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 13 Jun 2024 12:19:39 +0300 Subject: [PATCH 044/117] address pr comments --- .../Writers/TypeScript/CodeFunctionWriter.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 30fd08f9c9..6d27a2fa87 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -13,6 +13,7 @@ namespace Kiota.Builder.Writers.TypeScript; public class CodeFunctionWriter(TypeScriptConventionService conventionService) : BaseElementWriter(conventionService) { private static readonly HashSet customSerializationWriters = new(StringComparer.OrdinalIgnoreCase) { "writeObjectValue", "writeCollectionOfObjectValues" }; + private const string FactoryMethodReturnType = "((instance?: Parsable) => Record void>)"; public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter writer) { @@ -26,10 +27,8 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is CodeComposedTypeBase composedType && conventions.IsComposedOfPrimitives(composedType); - var factoryMethodReturnType = "((instance?: Parsable) => Record void>)"; - var returnType = codeMethod.Kind is CodeMethodKind.Factory || (codeMethod.Kind is CodeMethodKind.ComposedTypeFactory && !isComposedOfPrimitives) ? - factoryMethodReturnType : + FactoryMethodReturnType : GetTypescriptTypeString(codeMethod.ReturnType, codeElement, inlineComposedTypeString: true); var isVoid = "void".EqualsIgnoreCase(returnType); CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.OriginalLocalMethod, writer, isVoid, conventions); @@ -107,7 +106,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite return; } - WriteComposedTypeSerialization(composedParam, codeElement, writer); + WriteDefaultComposedTypeSerialization(composedParam, codeElement, writer); } private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) @@ -119,7 +118,7 @@ private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedT } } - private void WriteComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); var paramName = composedParam.Name.ToFirstCharacterLowerCase(); From 8f0f5d6c143c8914d3c4197694c5a7af25bbc43d Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 13 Jun 2024 17:27:08 +0300 Subject: [PATCH 045/117] address pr comments --- .../Refiners/TypeScriptRefiner.cs | 8 +-- .../Writers/TypeScript/CodeFunctionWriter.cs | 50 +++++++++++-------- .../TypeScript/TypeScriptConventionService.cs | 4 +- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 1a2bfbae7a..c9358ce161 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -338,7 +338,7 @@ private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterf RemoveOldCodeFunctionAndAddNewOne(children, codeInterface, codeNamespace, factoryMethod, factoryFunction); // Remove the deserializer import statement if its not being used - if (composedType is CodeUnionType || ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + if (composedType is CodeUnionType || IsComposedOfPrimitives(composedType)) { RemoveUnusedDeserializerImport(children, factoryFunction); } @@ -363,7 +363,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI if (deserializerMethod is null) return; // For code union Deserializer is not required, however its needed for Object Intersection types - if (composedType is CodeIntersectionType && !ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + if (composedType is CodeIntersectionType && !IsComposedOfPrimitives(composedType)) { var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); var deserializerFunction = new CodeFunction(method) { Name = method.Name }; @@ -384,7 +384,7 @@ private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface co private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) { var method = CreateCodeMethod(codeInterface, function); - if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + if (composedType is not null && IsComposedOfPrimitives(composedType)) method.ReturnType = composedType; return method; } @@ -393,7 +393,7 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co { var method = CreateCodeMethod(codeInterface, function); // Add the key parameter if the composed type is a union of primitive values - if (ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + if (IsComposedOfPrimitives(composedType)) method.AddParameter(CreateKeyParameter()); return method; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 6d27a2fa87..24f40efc78 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -25,7 +25,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; - var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is CodeComposedTypeBase composedType && conventions.IsComposedOfPrimitives(composedType); + var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); var returnType = codeMethod.Kind is CodeMethodKind.Factory || (codeMethod.Kind is CodeMethodKind.ComposedTypeFactory && !isComposedOfPrimitives) ? FactoryMethodReturnType : @@ -94,7 +94,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; - if (conventions.IsComposedOfPrimitives(composedType)) + if (IsComposedOfPrimitives(composedType)) { WriteComposedTypeSerializationForPrimitives(composedType, composedParam, codeElement, writer); return; @@ -209,14 +209,14 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when ConventionServiceInstance.IsComposedOfPrimitives(type): + case CodeComposedTypeBase type when IsComposedOfPrimitives(type): WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: WriteFactoryMethodBodyForCodeUnionType(codeElement, writer, parseNodeParameter); break; case CodeIntersectionType _ when parseNodeParameter != null: - WriteFactoryMethodBodyForCodeIntersectionType(codeElement, returnType, writer, parseNodeParameter); + WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); break; default: WriteNormalFactoryMethodBody(codeElement, returnType, writer); @@ -224,11 +224,6 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, } } - private void WriteFactoryMethodBodyForCodeIntersectionType(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter parseNodeParameter) - { - WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); - } - private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, LanguageWriter writer, CodeParameter parseNodeParameter) { WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); @@ -281,7 +276,7 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, CodeParameter? parseNodeParameter) { - if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is CodeComposedTypeBase composedType && conventions.IsComposedOfPrimitives(composedType) && parseNodeParameter is not null) + if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType) && parseNodeParameter is not null) { return $"({parseNodeParameter.Name.ToFirstCharacterLowerCase()})"; } @@ -370,30 +365,43 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { writer.WriteLine($"if({modelParamName}.{codePropertyName})"); } - if (composedType is not null && conventions.IsComposedOfPrimitives(composedType)) + if (composedType is not null && IsComposedOfPrimitives(composedType)) writer.WriteLine($"{serializationName}(writer, \"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); else writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); } } + public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { ArgumentNullException.ThrowIfNull(propertyType); ArgumentNullException.ThrowIfNull(method); - var composedType = GetOriginalComposedType(propertyType); - if (composedType is not null && ConventionServiceInstance.IsComposedOfPrimitives(composedType)) + var composedType = GetOriginalComposedType(propertyType); + if (composedType is not null && IsComposedOfPrimitives(composedType)) + { return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; + } var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; - return (currentType, propertyTypeName) switch + + if (currentType != null && !string.IsNullOrEmpty(propertyTypeName)) { - (CodeType type, string prop) when !string.IsNullOrEmpty(prop) && GetSerializationMethodNameForCodeType(type, prop) is string result && !string.IsNullOrWhiteSpace(result) => result, - (_, "string" or "boolean" or "number" or "Guid" or "Date" or "DateOnly" or "TimeOnly" or "Duration") => $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value", - _ => "writeObjectValue" - }; + var result = GetSerializationMethodNameForCodeType(currentType, propertyTypeName); + if (!string.IsNullOrWhiteSpace(result)) + { + return result; + } + } + + if (propertyTypeName is TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_BOOLEAN or TYPE_NUMBER or TYPE_GUID or TYPE_DATE or TYPE_DATE_ONLY or TYPE_TIME_ONLY or TYPE_DURATION) + { + return $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value"; + } + + return "writeObjectValue"; } private static CodeType GetCodeTypeForComposedType(CodeComposedTypeBase composedType) @@ -419,7 +427,8 @@ _ when ConventionServiceInstance.StreamTypeName.Equals(propertyType, StringCompa private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { - if (codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault() is CodeParameter param && param.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) + var param = codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault(); + if (param?.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) { var properties = codeInterface.Properties.Where(static x => x.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.BackingStore) && !x.ExistsInBaseType); @@ -434,8 +443,7 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer.CloseBlock(); } - else - throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); + else throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); } private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 72fc00d130..6dfaab8d0f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -91,10 +91,10 @@ public override string GetAccessModifier(AccessModifier access) }; } - public bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) + public static bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) { // Primitive values don't have a discriminator property so it should be handled differently if any of the values is primitive - return composedType?.Types.All(x => IsPrimitiveType(GetTypeString(x, composedType))) ?? false; + return composedType?.Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, composedType))) ?? false; } public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) From cc2e34bcd4fdc312e82ef2742cdb09c7a180f2e8 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 13 Jun 2024 18:50:46 +0300 Subject: [PATCH 046/117] address pr comments --- .../Writers/TypeScript/CodeFunctionWriter.cs | 16 ++++---- .../TypeScript/TypeScriptConventionService.cs | 40 ++++++++----------- .../Writers/TypeScript/TypeScriptWriter.cs | 3 +- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 24f40efc78..c2865a439f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -61,13 +61,13 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } - private static void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) + private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) { ArgumentNullException.ThrowIfNull(parseNodeParameter); var parseNodeParameterName = parseNodeParameter.Name.ToFirstCharacterLowerCase(); writer.StartBlock($"if ({parseNodeParameterName}) {{"); - string getPrimitiveValueString = string.Join(" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + string getPrimitiveValueString = string.Join(" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); writer.WriteLine($"return {getPrimitiveValueString};"); writer.CloseBlock(); writer.WriteLine("return undefined;"); @@ -288,7 +288,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM var functionName = GetFunctionName(returnType, kind); var parentNamespace = codeElement.GetImmediateParentOfType(); var codeFunction = FindCodeFunctionInParentNamespaces(functionName, parentNamespace); - return ConventionServiceInstance.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); + return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); } private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) @@ -373,7 +373,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro } - public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) + public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { ArgumentNullException.ThrowIfNull(propertyType); ArgumentNullException.ThrowIfNull(method); @@ -384,7 +384,7 @@ public static string GetSerializationMethodName(CodeTypeBase propertyType, CodeM return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; } - var propertyTypeName = ConventionServiceInstance.TranslateType(propertyType); + var propertyTypeName = TranslateTypescriptType(propertyType); CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; if (currentType != null && !string.IsNullOrEmpty(propertyTypeName)) @@ -414,12 +414,12 @@ private static CodeType GetCodeTypeForComposedType(CodeComposedTypeBase composed }; } - private static string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) + private string? GetSerializationMethodNameForCodeType(CodeType propType, string propertyType) { return propType switch { _ when propType.TypeDefinition is CodeEnum currentEnum => $"writeEnumValue<{currentEnum.Name.ToFirstCharacterUpperCase()}{(currentEnum.Flags && !propType.IsCollection ? "[]" : string.Empty)}>", - _ when ConventionServiceInstance.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue", + _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "writeByteArrayValue", _ when propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None => propType.TypeDefinition == null ? $"writeCollectionOfPrimitiveValues<{propertyType}>" : "writeCollectionOfObjectValues", _ => null }; @@ -481,7 +481,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str else { var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 6dfaab8d0f..04d0fc1231 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -12,16 +12,6 @@ namespace Kiota.Builder.Writers.TypeScript; public class TypeScriptConventionService : CommonLanguageConventionService { - private static TypeScriptConventionService? conventionService; - public static TypeScriptConventionService ConventionServiceInstance - { - get - { - conventionService ??= new TypeScriptConventionService(); - return conventionService; - } - } - #pragma warning disable CA1707 // Remove the underscores public const string TYPE_INTEGER = "integer"; public const string TYPE_INT64 = "int64"; @@ -144,7 +134,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ var typeName = GetTypeAlias(currentType, targetElement) is string alias && !string.IsNullOrEmpty(alias) ? alias - : ConventionServiceInstance.TranslateType(currentType); + : TranslateTypescriptType(currentType); var genericParameters = currentType.GenericTypeParameterValues.Count != 0 ? $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation)))}>" @@ -195,6 +185,11 @@ public static string TranslateType(CodeComposedTypeBase composedType) } public override string TranslateType(CodeType type) + { + return TranslateTypescriptType(type); + } + + public static string TranslateTypescriptType(CodeTypeBase type) { return type?.Name switch { @@ -203,7 +198,8 @@ public override string TranslateType(CodeType type) TYPE_GUID => TYPE_GUID, TYPE_STRING or TYPE_OBJECT or TYPE_BOOLEAN or TYPE_VOID or TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_OBJECT or TYPE_LOWERCASE_BOOLEAN or TYPE_LOWERCASE_VOID => type.Name.ToFirstCharacterLowerCase(), null => TYPE_OBJECT, - _ => GetCodeTypeName(type) is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : TYPE_OBJECT, + _ when type is CodeType codeType => GetCodeTypeName(codeType) is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : TYPE_OBJECT, + _ => throw new InvalidOperationException($"Unable to translate type {type.Name}") }; } @@ -285,17 +281,13 @@ private string GetDeprecationComment(IDeprecableElement element) public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter? writer = null) { var composedType = GetOriginalComposedType(targetClassType); - string targetClassName = composedType is not null ? TranslateType(composedType) : ConventionServiceInstance.TranslateType(targetClassType); + string targetClassName = composedType is not null ? TranslateType(composedType) : TranslateTypescriptType(targetClassType); var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; - if (ConventionServiceInstance.GetTypeString(targetClassType, currentElement, false, writer) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; - if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) + if (GetTypescriptTypeString(targetClassType, currentElement, false, writer) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; + if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass && GetFactoryMethod(definitionClass, resultName) is { } factoryMethod) { - var factoryMethod = GetFactoryMethod(definitionClass, resultName); - if (factoryMethod != null) - { - var methodName = ConventionServiceInstance.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); - return methodName.ToFirstCharacterUpperCase();// static function is aliased - } + var methodName = GetTypescriptTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); + return methodName.ToFirstCharacterUpperCase();// static function is aliased } throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); } @@ -319,12 +311,12 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName); } - public static string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method) + public string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method) { ArgumentNullException.ThrowIfNull(codeType); ArgumentNullException.ThrowIfNull(method); var isCollection = codeType.CollectionKind != CodeTypeCollectionKind.None; - var propertyType = ConventionServiceInstance.GetTypeString(codeType, method, false); + var propertyType = GetTypescriptTypeString(codeType, method, false); CodeTypeBase _codeType = GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : codeType; @@ -333,7 +325,7 @@ public static string GetDeserializationMethodName(CodeTypeBase codeType, CodeMet return (currentType.TypeDefinition, isCollection, propertyType) switch { (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", - (_, _, string prop) when ConventionServiceInstance.StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", + (_, _, string prop) when StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(_codeType, method)})", _ => GetDeserializationMethodNameForPrimitiveOrObject(_codeType, propertyType, method) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index cf9f398dea..45f8ccbd72 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -1,5 +1,4 @@ using Kiota.Builder.PathSegmenters; -using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Writers.TypeScript; @@ -8,7 +7,7 @@ public class TypeScriptWriter : LanguageWriter public TypeScriptWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new TypeScriptPathSegmenter(rootPath, clientNamespaceName); - var conventionService = ConventionServiceInstance; + var conventionService = new TypeScriptConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); AddOrReplaceCodeElementWriter(new CodeBlockEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); From b3e5655c2b20618f19e1ece7259516a814d0347d Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 14 Jun 2024 11:21:21 +0300 Subject: [PATCH 047/117] remove reference to external urls on tests --- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index b266e8c62b..8e1b1b0ce8 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1137,7 +1137,7 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1210,7 +1210,7 @@ export function createPetsPatchRequestBodyFromDiscriminatorValue(parseNode: Pars var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1247,7 +1247,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1288,7 +1288,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pets", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1337,7 +1337,7 @@ export function createFooBarFromDiscriminatorValue(parseNode: ParseNode | undefi var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1374,7 +1374,7 @@ public async Task Writes_CodeIntersectionType_DeserializerFunctions() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1412,7 +1412,7 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", OpenAPIFilePath = "https://api.example.com/v1", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "FooBar", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); From 8eddb41ea3144bde91d2536d9e57ae39b7157358 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 18 Jun 2024 15:05:42 +0300 Subject: [PATCH 048/117] simplify open api sample test files --- .../CodeIntersectionTypeSampleYml.cs | 3 + .../OpenApiSampleFiles/GithubRepos.cs | 303 ------------------ .../OpenApiSampleFiles/PetsUnion.cs | 3 + .../UnionOfPrimitiveValuesSample.cs | 31 ++ .../TypeScriptLanguageRefinerTests.cs | 10 +- .../TypeScript/CodeFunctionWriterTests.cs | 20 +- 6 files changed, 52 insertions(+), 318 deletions(-) delete mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs create mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveValuesSample.cs diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs index 7894c6155b..218e0ca0e4 100644 --- a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/CodeIntersectionTypeSampleYml.cs @@ -2,6 +2,9 @@ public static class CodeIntersectionTypeSampleYml { + /** + * An OpenAPI 3.0.1 sample document with intersection object types, comprising an intersection of Foo and Bar. + */ public static readonly string OpenApiYaml = @" openapi: 3.0.3 info: diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs deleted file mode 100644 index e86a94a108..0000000000 --- a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/GithubRepos.cs +++ /dev/null @@ -1,303 +0,0 @@ -namespace Kiota.Builder.Tests.OpenApiSampleFiles; - - -public static class GithubRepos -{ - public static readonly string OpenApiYaml = @" -openapi: 3.0.0 -info: - title: GitHub API - description: API for managing GitHub organizations and repositories - version: 1.0.0 - contact: - name: GitHub API Support - url: https://support.github.com/contact - email: support@github.com -servers: - - url: https://api.github.com -paths: - /orgs/{org}/repos: - post: - description: > - Creates a new repository in the specified organization. The authenticated user must be a member of the organization. - - **OAuth scope requirements** - - When using [OAuth](https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/), authorizations must include: - - * `public_repo` scope or `repo` scope to create a public repository. Note: For GitHub AE, use `repo` scope to create an internal repository. - * `repo` scope to create a private repository - externalDocs: - description: API method documentation - url: https://docs.github.com/rest/reference/repos#create-an-organization-repository - operationId: repos/create-in-org - parameters: - - $ref: '#/components/parameters/org' - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - name - properties: - allow_auto_merge: - type: boolean - default: false - description: Either `true` to allow auto-merge on pull requests, or `false` to disallow auto-merge. - delete_branch_on_merge: - type: boolean - default: false - description: > - Either `true` to allow automatically deleting head branches when pull requests are merged, or `false` to prevent automatic deletion. - **The authenticated user must be an organization owner to set this property to `true`.** - description: - type: string - description: A short description of the repository. - gitignore_template: - type: string - description: > - Desired language or platform [.gitignore template](https://github.com/github/gitignore) to apply. - Use the name of the template without the extension. For example, ""Haskell"". - has_downloads: - type: boolean - default: true - description: Whether downloads are enabled. - merge_commit_message: - type: string - enum: - - PR_BODY - - PR_TITLE - - BLANK - description: > - The default value for a merge commit message. - - `PR_TITLE` - default to the pull request's title. - - `PR_BODY` - default to the pull request's body. - - `BLANK` - default to a blank commit message. - squash_merge_commit_message: - type: string - enum: - - PR_BODY - - COMMIT_MESSAGES - - BLANK - description: > - The default value for a squash merge commit message: - - `PR_BODY` - default to the pull request's body. - - `COMMIT_MESSAGES` - default to the branch's commit messages. - - `BLANK` - default to a blank commit message. - squash_merge_commit_title: - type: string - enum: - - PR_TITLE - - COMMIT_OR_PR_TITLE - description: > - The default value for a squash merge commit title: - - `PR_TITLE` - default to the pull request's title. - - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull request's title (when more than one commit). - team_id: - type: integer - description: > - The id of the team that will be granted access to this repository. This is only valid when creating a repository in an organization. - use_squash_pr_title_as_default: - type: boolean - default: false - deprecated: true - description: > - Either `true` to allow squash-merge commits to use pull request title, or `false` to use commit message. - **This property has been deprecated. Please use `squash_merge_commit_title` instead. - visibility: - type: string - enum: - - public - - private - description: The visibility of the repository. - responses: - '201': - description: Response - content: - application/json: - examples: - default: - $ref: '#/components/examples/repository' - schema: - $ref: '#/components/schemas/repository' - headers: - Location: - schema: - type: string - example: 'https://api.github.com/repos/octocat/Hello-World' - '403': - $ref: '#/components/responses/forbidden' - '422': - $ref: '#/components/responses/validation_failed' - summary: Create an organization repository - tags: - - repos - x-github: - category: repos - enabledForGitHubApps: true - githubCloudOnly: false - subcategory: null -components: - parameters: - org: - name: org - in: path - description: Organization name - required: true - schema: - type: string - responses: - forbidden: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/basic-error' - validation_failed: - description: Validation failed, or the endpoint has been spammed. - content: - application/json: - schema: - $ref: '#/components/schemas/validation-error' - schemas: - basic-error: - title: Basic Error - description: Basic Error - type: object - properties: - documentation_url: - type: string - message: - type: string - status: - type: string - url: - type: string - repository: - title: Repository - description: Repository - type: object - properties: - archived: - type: boolean - assignees_url: - type: string - branches_url: - type: string - collaborators_url: - type: string - full_name: - type: string - git_url: - type: string - labels_url: - type: string - language: - type: string - releases_url: - type: string - score: - type: integer - size: - type: integer - example: | - full_name: octocat/Hello-World - private: false - owner: - login: octocat - id: 1 - node_id: MDQ6VXNlcjE= - avatar_url: 'https://github.com/images/error/octocat_happy.gif' - gravatar_id: '' - url: 'https://api.github.com/users/octocat' - html_url: 'https://github.com/octocat' - followers_url: 'https://api.github.com/users/octocat/followers' - following_url: 'https://api.github.com/users/octocat/following{/other_user}' - gists_url: 'https://api.github.com/users/octocat/gists{/gist_id}' - starred_url: 'https://api.github.com/users/octocat/starred{/owner}{/repo}' - subscriptions_url: 'https://api.github.com/users/octocat/subscriptions' - organizations_url: 'https://api.github.com/users/octocat/orgs' - repos_url: 'https://api.github.com/users/octocat/repos' - events_url: 'https://api.github.com/users/octocat/events{/privacy}' - received_events_url: 'https://api.github.com/users/octocat/received_events' - type: User - site_admin: false - html_url: 'https://github.com/octocat/Hello-World' - description: This is your first repository - fork: false - url: 'https://api.github.com/repos/octocat/Hello-World' - created_at: '2011-01-26T19:01:12Z' - updated_at: '2011-01-26T19:14:43Z' - pushed_at: '2011-01-26T19:06:43Z' - homepage: 'https://github.com' - size: 100 - stargazers_count: 80 - watchers_count: 80 - language: JavaScript - has_issues: true - has_projects: true - has_downloads: true - has_wiki: true - has_pages: false - forks_count: 9 - mirror_url: null - archived: false - disabled: false - open_issues_count: 0 - license: - key: mit - name: MIT License - spdx_id: MIT - url: 'https://api.github.com/licenses/mit' - node_id: MDc6TGljZW5zZTEz - forks: 9 - open_issues: 0 - watchers: 80 - default_branch: master - validation-error: - title: Validation Error - description: Validation Error - type: object - required: - - message - - documentation_url - properties: - documentation_url: - type: string - errors: - type: array - items: - type: object - required: - - code - properties: - code: - type: string - field: - type: string - index: - type: integer - message: - type: string - resource: - type: string - value: - oneOf: - - type: string - nullable: true - - type: integer - nullable: true - - type: array - items: - type: string - nullable: true - message: - type: string - example: - documentation_url: 'https://developer.github.com/v3/activity/events/types/#watchevent' - message: 'You need to be signed in to watch a repository' - "; - -} diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs index cd1eea9b86..944ac4e776 100644 --- a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/PetsUnion.cs @@ -3,6 +3,9 @@ public static class PetsUnion { + /** + * An OpenAPI 3.0.1 sample document with a union of objects, comprising a union of Cats and Dogs. + */ public static readonly string OpenApiYaml = @" openapi: 3.0.0 info: diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveValuesSample.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveValuesSample.cs new file mode 100644 index 0000000000..e92e444b21 --- /dev/null +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveValuesSample.cs @@ -0,0 +1,31 @@ +namespace Kiota.Builder.Tests.OpenApiSampleFiles; + + +public static class UnionOfPrimitiveValuesSample +{ + /** + * An OpenAPI 3.0.1 sample document with a union of primitive values, comprising a union of stings and numbers. + */ + public static readonly string Yaml = @" +openapi: 3.0.1 +info: + title: Example of UnionTypes + version: 1.0.0 +paths: + /primitives: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/primitives' +components: + schemas: + primitives: + oneOf: + - type: string + - type: number"; + +} diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 6ed6d1a910..4ecf98d6ec 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -860,9 +860,9 @@ public async Task ParsesAndRefinesUnionOfPrimitiveValues() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveValuesSample.Yaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", OpenAPIFilePath = "https://api.apis.guru/v2/specs/github.com/api.github.com/1.1.4/openapi.json", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Primitives", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -870,7 +870,7 @@ public async Task ParsesAndRefinesUnionOfPrimitiveValues() var codeModel = builder.CreateSourceModel(node); var rootNS = codeModel.FindNamespaceByName("ApiSdk"); Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Github", false); + var clientBuilder = rootNS.FindChildByName("Primitives", false); Assert.NotNull(clientBuilder); var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); Assert.NotNull(constructor); @@ -878,9 +878,9 @@ public async Task ParsesAndRefinesUnionOfPrimitiveValues() Assert.Empty(constructor.DeserializerModules); await ILanguageRefiner.Refine(generationConfiguration, rootNS); Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + var modelCodeFile = modelsNS.FindChildByName("primitivesRequestBuilder", false); Assert.NotNull(modelCodeFile); var unionType = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && TypeScriptRefiner.GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null).ToList(); Assert.True(unionType.Count > 0); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 8e1b1b0ce8..59e5697067 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1135,9 +1135,9 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveValuesSample.Yaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Primitives", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1145,7 +1145,7 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() var codeModel = builder.CreateSourceModel(node); var rootNS = codeModel.FindNamespaceByName("ApiSdk"); Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Github", false); + var clientBuilder = rootNS.FindChildByName("Primitives", false); Assert.NotNull(clientBuilder); var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); Assert.NotNull(constructor); @@ -1153,9 +1153,9 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() Assert.Empty(constructor.DeserializerModules); await ILanguageRefiner.Refine(generationConfiguration, rootNS); Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + var modelCodeFile = modelsNS.FindChildByName("primitivesRequestBuilder", false); Assert.NotNull(modelCodeFile); /* @@ -1245,9 +1245,9 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, GithubRepos.OpenApiYaml); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveValuesSample.Yaml); var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Github", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Primitives", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); @@ -1255,7 +1255,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() var codeModel = builder.CreateSourceModel(node); var rootNS = codeModel.FindNamespaceByName("ApiSdk"); Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Github", false); + var clientBuilder = rootNS.FindChildByName("Primitives", false); Assert.NotNull(clientBuilder); var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); Assert.NotNull(constructor); @@ -1263,9 +1263,9 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() Assert.Empty(constructor.DeserializerModules); await ILanguageRefiner.Refine(generationConfiguration, rootNS); Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName(generationConfiguration.ModelsNamespaceName); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName(IndexFileName, false); + var modelCodeFile = modelsNS.FindChildByName("primitivesRequestBuilder", false); Assert.NotNull(modelCodeFile); // Test Serializer function From 5ba007cfe9e917bfb643db643bdf297c9684a1eb Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 20 Jun 2024 10:47:13 +0300 Subject: [PATCH 049/117] address pr comments --- .../Refiners/TypeScriptRefiner.cs | 15 +++----- .../TypeScript/CodeFunctionWriterTests.cs | 37 ++++--------------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index c9358ce161..d95624ebd3 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -258,7 +258,7 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && private static CodeFile? GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { - var functions = GetRelevantFunctions(codeInterface, codeNamespace).ToArray(); + var functions = GetSerializationAndFactoryFunctions(codeInterface, codeNamespace).ToArray(); if (functions.Length == 0) return null; @@ -269,7 +269,7 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && return codeNamespace.TryAddCodeFile(codeInterface.Name, elements.ToArray()); } - private static IEnumerable GetRelevantFunctions(CodeInterface codeInterface, CodeNamespace codeNamespace) + private static IEnumerable GetSerializationAndFactoryFunctions(CodeInterface codeInterface, CodeNamespace codeNamespace) { return codeNamespace.GetChildElements(true) .OfType() @@ -281,7 +281,7 @@ private static IEnumerable GetRelevantFunctions(CodeInterface code private static bool IsRelevantDeserializerOrSerializer(CodeFunction codeFunction, CodeInterface codeInterface) { return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Serializer && - codeFunction.OriginalLocalMethod.Parameters.Any(x => x.Type.Name.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase)); + codeFunction.OriginalLocalMethod.Parameters.Any(x => x.Type is CodeType codeType && codeType.TypeDefinition == codeInterface); } private static bool IsRelevantFactory(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) @@ -308,7 +308,7 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac private static CodeFunction? FindFunctionOfKind(List elements, CodeMethodKind kind) { - return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.Kind == kind); + return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.IsOfKind(kind)); } private static void RemoveOldCodeFunctionAndAddNewOne(List children, CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction oldCodeFunction, CodeFunction newCodeFunction) @@ -320,16 +320,13 @@ private static void RemoveOldCodeFunctionAndAddNewOne(List children private static void RemoveUnusedDeserializerImport(List children, CodeFunction factoryFunction) { - var deserializerMethod = FindFunctionOfKind(children, CodeMethodKind.Deserializer); - if (deserializerMethod is not null) + if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is {} deserializerMethod) factoryFunction.RemoveUsingsByDeclarationName(deserializerMethod.Name); } private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var factoryMethod = FindFunctionOfKind(children, CodeMethodKind.Factory); - - if (factoryMethod is null) return; + if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not {} factoryMethod) return; var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 59e5697067..98fdfe0fd4 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1186,26 +1186,6 @@ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | un [Fact] public async Task Writes_UnionOfObjects_FactoryMethod() { - var expectedResultString = @"/** - * Creates a new instance of the appropriate class based on discriminator value - * @param parseNode The parse node to use to read the discriminator value and create the object - * @returns {Cat | Dog} - */ -export function createPetsPatchRequestBodyFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record void>) { - const mappingValueNode = parseNode.getChildNode(""pet_type""); - if (mappingValueNode) { - const mappingValue = mappingValueNode.getStringValue(); - if (mappingValue) { - switch (mappingValue) { - case ""Cat"": - return deserializeIntoCat; - case ""Dog"": - return deserializeIntoDog; - } - } - } - throw new Error(""A discriminator property is required to distinguish a union type""); -"; var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, PetsUnion.OpenApiYaml); @@ -1236,7 +1216,11 @@ export function createPetsPatchRequestBodyFromDiscriminatorValue(parseNode: Pars Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); - Assert.Contains(expectedResultString, result); + Assert.Contains("if (mappingValue)", result); + Assert.Contains("case \"Cat\":", result); + Assert.Contains("return deserializeIntoCat;", result); + Assert.Contains("case \"Dog\":", result); + Assert.Contains("return deserializeIntoDog;", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } @@ -1325,14 +1309,6 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() [Fact] public async Task Writes_CodeIntersectionType_FactoryMethod() { - var expectedFactoryFunctionString = @"/** - * Creates a new instance of the appropriate class based on discriminator value - * @param parseNode The parse node to use to read the discriminator value and create the object - * @returns {Bar & Foo} - */ -export function createFooBarFromDiscriminatorValue(parseNode: ParseNode | undefined) : ((instance?: Parsable) => Record void>) { - return deserializeIntoFooBar; -"; var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); await File.WriteAllTextAsync(tempFilePath, CodeIntersectionTypeSampleYml.OpenApiYaml); @@ -1363,7 +1339,8 @@ export function createFooBarFromDiscriminatorValue(parseNode: ParseNode | undefi Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); - Assert.Contains(expectedFactoryFunctionString, result); + Assert.Contains("export function createFooBarFromDiscriminatorValue(", result); + Assert.Contains("return deserializeIntoFooBar;", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } From 9665e19a5e2e4acf04a41b7661fcba1aef9024f6 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 20 Jun 2024 11:51:24 +0300 Subject: [PATCH 050/117] address pr comments, format code --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 4 ++-- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index d95624ebd3..4e47a8109b 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -320,13 +320,13 @@ private static void RemoveOldCodeFunctionAndAddNewOne(List children private static void RemoveUnusedDeserializerImport(List children, CodeFunction factoryFunction) { - if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is {} deserializerMethod) + if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is { } deserializerMethod) factoryFunction.RemoveUsingsByDeclarationName(deserializerMethod.Name); } private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not {} factoryMethod) return; + if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } factoryMethod) return; var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index c2865a439f..ea236608a1 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -120,7 +120,9 @@ private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedT private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { - var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName ?? throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); + var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName; + if (string.IsNullOrEmpty(discriminatorPropertyName)) + throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); var paramName = composedParam.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"if ({paramName} === undefined) return;"); writer.StartBlock($"switch ({paramName}.{discriminatorPropertyName}) {{"); From 63abe19159b7872146d071f2fda19458ccead97b Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 20 Jun 2024 12:32:38 +0300 Subject: [PATCH 051/117] address pr comments --- .../Writers/TypeScript/CodeFunctionWriter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index ea236608a1..5cb8d776c6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -295,13 +295,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) { - CodeFunction? codeFunction; - do - { - codeFunction = parentNamespace?.FindChildByName(functionName); - parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); - } while (!functionName.Equals(codeFunction?.Name, StringComparison.Ordinal) && parentNamespace is not null); - return codeFunction; + return parentNamespace?.GetRootNamespace()?.FindChildByName(functionName); } private static string GetFunctionName(string returnType, CodeMethodKind functionKind) From edf1a7f83f9b130e8e600df4322327cb4a1d9838 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 20 Jun 2024 13:02:03 +0300 Subject: [PATCH 052/117] fix failing test --- .../Writers/TypeScript/CodeFunctionWriter.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 5cb8d776c6..ea236608a1 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -295,7 +295,13 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) { - return parentNamespace?.GetRootNamespace()?.FindChildByName(functionName); + CodeFunction? codeFunction; + do + { + codeFunction = parentNamespace?.FindChildByName(functionName); + parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); + } while (!functionName.Equals(codeFunction?.Name, StringComparison.Ordinal) && parentNamespace is not null); + return codeFunction; } private static string GetFunctionName(string returnType, CodeMethodKind functionKind) From d334d4cb2f117996a9511a2d8c4d91ab2a0353a9 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 21 Jun 2024 12:23:08 +0300 Subject: [PATCH 053/117] address pr comments --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 21 ++++++++++++++++ .../Refiners/TypeScriptRefiner.cs | 25 +++---------------- .../Writers/TypeScript/CodeFunctionWriter.cs | 19 ++++++++------ 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 96223616cc..8375416530 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -123,6 +123,27 @@ public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func(); var codeFunction = FindCodeFunctionInParentNamespaces(functionName, parentNamespace); return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); @@ -295,16 +295,19 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) { - CodeFunction? codeFunction; - do + CodeFunction? codeFunction = null; + + for (var currentNamespace = parentNamespace; + currentNamespace is not null && !functionName.Equals(codeFunction?.Name, StringComparison.Ordinal); + currentNamespace = currentNamespace.Parent?.GetImmediateParentOfType()) { - codeFunction = parentNamespace?.FindChildByName(functionName); - parentNamespace = parentNamespace?.Parent?.GetImmediateParentOfType(); - } while (!functionName.Equals(codeFunction?.Name, StringComparison.Ordinal) && parentNamespace is not null); + codeFunction = currentNamespace.FindChildByName(functionName); + } + return codeFunction; } - private static string GetFunctionName(string returnType, CodeMethodKind functionKind) + private static string CreateSerializationFunctionNameFromType(string returnType, CodeMethodKind functionKind) { return functionKind switch { From 3ecff4eb35d53ea9a3cc39633641d6b41ff772d4 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 21 Jun 2024 12:46:51 +0300 Subject: [PATCH 054/117] format code --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 5f65cdeddc..58eb013175 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -297,8 +297,8 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM { CodeFunction? codeFunction = null; - for (var currentNamespace = parentNamespace; - currentNamespace is not null && !functionName.Equals(codeFunction?.Name, StringComparison.Ordinal); + for (var currentNamespace = parentNamespace; + currentNamespace is not null && !functionName.Equals(codeFunction?.Name, StringComparison.Ordinal); currentNamespace = currentNamespace.Parent?.GetImmediateParentOfType()) { codeFunction = currentNamespace.FindChildByName(functionName); From ca06ae99131781d99ab814522095b2a22234db2d Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 21 Jun 2024 18:05:57 +0300 Subject: [PATCH 055/117] apply pr suggestion to other places for consistency --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index b08a418334..8e3be902fe 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -343,8 +343,7 @@ private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterf private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var function = FindFunctionOfKind(children, CodeMethodKind.Serializer); - if (function is null) return; + if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; var method = CreateSerializerMethodForComposedType(codeInterface, function, composedType); var serializerFunction = new CodeFunction(method) { Name = method.Name }; @@ -355,9 +354,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInt private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { - var deserializerMethod = FindFunctionOfKind(children, CodeMethodKind.Deserializer); - - if (deserializerMethod is null) return; + if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; // For code union Deserializer is not required, however its needed for Object Intersection types if (composedType is CodeIntersectionType && !IsComposedOfPrimitives(composedType)) From 799b0d6b67d95da8871ce07d8c4c193cd08b19e3 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 25 Jun 2024 10:30:16 +0300 Subject: [PATCH 056/117] enable supressed typescript tests --- it/config.json | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/it/config.json b/it/config.json index 13468a0cde..d9984f61c4 100644 --- a/it/config.json +++ b/it/config.json @@ -43,10 +43,6 @@ } ], "Suppressions": [ - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - }, { "Language": "ruby", "Rationale": "https://github.com/microsoft/kiota/issues/1816" @@ -156,10 +152,6 @@ "Language": "go", "Rationale": "https://github.com/microsoft/kiota/issues/3436" }, - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - }, { "Language": "ruby", "Rationale": "https://github.com/microsoft/kiota/issues/2484" @@ -170,10 +162,6 @@ } ], "IdempotencySuppressions": [ - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - } ] }, "apisguru::twilio.com:api": { @@ -246,10 +234,6 @@ }, "apisguru::stripe.com": { "Suppressions": [ - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - }, { "Language": "go", "Rationale": "https://github.com/microsoft/kiota/issues/2834" @@ -280,10 +264,6 @@ "Language": "python", "Rationale": "https://github.com/microsoft/kiota/issues/2842" }, - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - }, { "Language": "ruby", "Rationale": "https://github.com/microsoft/kiota/issues/1816" @@ -312,20 +292,12 @@ }, "apisguru::twitter.com:current": { "Suppressions": [ - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - }, { "Language": "ruby", "Rationale": "https://github.com/microsoft/kiota/issues/1816" } ], "IdempotencySuppressions": [ - { - "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" - } ] }, "apisguru::apis.guru": { From fc8e8920a8359aa06d133b06e2ebda16fce35b0f Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 25 Jun 2024 15:56:43 +0300 Subject: [PATCH 057/117] allow generation to constinue when the discriminator property is missing --- .../Writers/TypeScript/CodeFunctionWriter.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 58eb013175..a05deb6271 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -120,14 +120,33 @@ private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedT private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { - var discriminatorPropertyName = codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName; + var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; + var discriminatorPropertyName = discriminatorInfo.DiscriminatorPropertyName; + if (string.IsNullOrEmpty(discriminatorPropertyName)) - throw new InvalidOperationException("Discriminator property name is required for composed type serialization"); + { + WriteMissingDiscriminatorPropertyComment(composedParam, codeElement, writer); + return; + } + var paramName = composedParam.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"if ({paramName} === undefined) return;"); - writer.StartBlock($"switch ({paramName}.{discriminatorPropertyName}) {{"); + WriteDiscriminatorSwitchBlock(discriminatorInfo, paramName, codeElement, writer); + } - foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) + private void WriteMissingDiscriminatorPropertyComment(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + { + var typeString = GetTypescriptTypeString(composedParam.Type, codeElement, inlineComposedTypeString: true); + var comment = $"The composed parameter '{composedParam.Name}' consists of {typeString}. However, it lacks a discriminator property, which is necessary for proper type differentiation. Please update the OpenAPI specification to include a discriminator property to ensure correct method generation."; + writer.WriteLine($"// {comment}"); + writer.WriteLine("return;"); + } + + private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminatorInfo, string paramName, CodeFunction codeElement, LanguageWriter writer) + { + writer.StartBlock($"switch ({paramName}.{discriminatorInfo.DiscriminatorPropertyName}) {{"); + + foreach (var mappedType in discriminatorInfo.DiscriminatorMappings) { writer.StartBlock($"case \"{mappedType.Key}\":"); var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); From 89b5a879b931ac7f2a2378a5044f829f9964cc1e Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 25 Jun 2024 17:28:43 +0300 Subject: [PATCH 058/117] fix failing it tests --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index a05deb6271..1f5602c730 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -498,7 +498,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; if (otherProp.Kind is CodePropertyKind.BackingStore) writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); - else if (GetOriginalComposedType(otherProp.Type) is not null) + else if (GetOriginalComposedType(otherProp.Type) is { } composedType && IsComposedOfPrimitives(composedType)) { writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); } From 9797869c8eb79a59fd4f974e4114728fa3325c6e Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 25 Jun 2024 17:48:36 +0300 Subject: [PATCH 059/117] fix it test compilation error 'parseNode' is possibly 'undefined' for apisguru::twitter.com:current --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 1f5602c730..532836389c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -276,7 +276,7 @@ private static bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParameter parseNodeParameter, LanguageWriter writer) { - writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}.getChildNode(\"{codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName}\");", + writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}?.getChildNode(\"{codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName}\");", "if (mappingValueNode) {"); writer.IncreaseIndent(); writer.WriteLines("const mappingValue = mappingValueNode.getStringValue();", From 1b8cb7e7465cc029acc848137d6e1439fe26277d Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 12:01:52 +0300 Subject: [PATCH 060/117] fix failing test --- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 98fdfe0fd4..26e5e5fd3f 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -124,7 +124,7 @@ public async Task WritesModelFactoryBody() parentNS.TryAddCodeFile("foo", factoryFunction); writer.Write(factoryFunction); var result = tw.ToString(); - Assert.Contains("const mappingValueNode = parseNode.getChildNode(\"@odata.type\")", result); + Assert.Contains("const mappingValueNode = parseNode?.getChildNode(\"@odata.type\")", result); Assert.Contains("if (mappingValueNode) {", result); Assert.Contains("const mappingValue = mappingValueNode.getStringValue()", result); Assert.Contains("if (mappingValue) {", result); From f62886a8e0f8fcf278acc2d40ccb5714a39027c7 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 14:14:16 +0300 Subject: [PATCH 061/117] fix failing it test --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 532836389c..bc6568e346 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -434,7 +434,8 @@ private static CodeType GetCodeTypeForComposedType(CodeComposedTypeBase composed return new CodeType { Name = composedType.Name, - TypeDefinition = composedType + TypeDefinition = composedType, + CollectionKind = composedType.CollectionKind }; } From ce4b4a1ff052a7b6cd891151aaf5d4625019d200 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 14:46:02 +0300 Subject: [PATCH 062/117] ignore null default values --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index bc6568e346..d816d2629f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -505,7 +505,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str } else { - var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; + var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("null") ? $" ?? {dft}" : string.Empty; writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); } } From 1df1e8f8b95a2413b248675de19f569dee1195d6 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 15:40:44 +0300 Subject: [PATCH 063/117] ignore 'null' default values --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index d816d2629f..8eb38885b4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -505,7 +505,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str } else { - var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("null") ? $" ?? {dft}" : string.Empty; + var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); } } From 6df873de0cd5e36af92d210c01afa8b7fa363207 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 16:17:12 +0300 Subject: [PATCH 064/117] omit null from default values while serializing --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 8eb38885b4..96d2e0a24e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -374,7 +374,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); var serializationName = GetSerializationMethodName(codeProperty.Type, codeFunction.OriginalLocalMethod); - var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; + var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; var composedType = GetOriginalComposedType(codeProperty.Type); From 9b0f3eb93d0429e360cf6dd059510567f53b8aa4 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 26 Jun 2024 17:15:30 +0300 Subject: [PATCH 065/117] exclude apisguru::stripe.com integration test from running since it contains intersection type between objects and primitive values which isn't supported in typescript --- it/config.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/it/config.json b/it/config.json index d9984f61c4..5ba33e8ca5 100644 --- a/it/config.json +++ b/it/config.json @@ -234,6 +234,10 @@ }, "apisguru::stripe.com": { "Suppressions": [ + { + "Language": "typescript", + "Rationale": "This document includes an intersection between objects and primitive values, which is not supported in the TypeScript language and results in compilation errors." + }, { "Language": "go", "Rationale": "https://github.com/microsoft/kiota/issues/2834" From d3e9bb02097eae5a59debe2e457382f7ab4944b4 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 27 Jun 2024 12:30:45 +0300 Subject: [PATCH 066/117] address pr comments --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 2 +- .../Writers/TypeScript/TypeScriptConventionService.cs | 9 ++------- .../TypeScript/TypeScriptConventionServiceTests.cs | 5 +++-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 8e3be902fe..08f4f6e96e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -359,7 +359,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI // For code union Deserializer is not required, however its needed for Object Intersection types if (composedType is CodeIntersectionType && !IsComposedOfPrimitives(composedType)) { - var method = CreateDeserializerMethodForComposedType(codeInterface, deserializerMethod); + var method = CodeMethod.FromCodeFunctionAndInterface(codeInterface, deserializerMethod, GetComposedTypeMethodKind(deserializerMethod)); var deserializerFunction = new CodeFunction(method) { Name = method.Name }; deserializerFunction.AddUsing(deserializerMethod.Usings.ToArray()); children.Add(deserializerFunction); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 96d2e0a24e..cb61d6ddea 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -134,7 +134,7 @@ private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, WriteDiscriminatorSwitchBlock(discriminatorInfo, paramName, codeElement, writer); } - private void WriteMissingDiscriminatorPropertyComment(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + private static void WriteMissingDiscriminatorPropertyComment(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var typeString = GetTypescriptTypeString(composedParam.Type, codeElement, inlineComposedTypeString: true); var comment = $"The composed parameter '{composedParam.Name}' consists of {typeString}. However, it lacks a discriminator property, which is necessary for proper type differentiation. Please update the OpenAPI specification to include a discriminator property to ensure correct method generation."; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 04d0fc1231..c0aa01b749 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -178,12 +178,6 @@ private static string GetTypeAlias(CodeType targetType, CodeElement targetElemen return string.Empty; } - public static string TranslateType(CodeComposedTypeBase composedType) - { - ArgumentNullException.ThrowIfNull(composedType); - return composedType.Name.ToFirstCharacterUpperCase(); - } - public override string TranslateType(CodeType type) { return TranslateTypescriptType(type); @@ -198,6 +192,7 @@ public static string TranslateTypescriptType(CodeTypeBase type) TYPE_GUID => TYPE_GUID, TYPE_STRING or TYPE_OBJECT or TYPE_BOOLEAN or TYPE_VOID or TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_OBJECT or TYPE_LOWERCASE_BOOLEAN or TYPE_LOWERCASE_VOID => type.Name.ToFirstCharacterLowerCase(), null => TYPE_OBJECT, + _ when type is CodeComposedTypeBase composedType => composedType.Name.ToFirstCharacterUpperCase(), _ when type is CodeType codeType => GetCodeTypeName(codeType) is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : TYPE_OBJECT, _ => throw new InvalidOperationException($"Unable to translate type {type.Name}") }; @@ -281,7 +276,7 @@ private string GetDeprecationComment(IDeprecableElement element) public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElement currentElement, LanguageWriter? writer = null) { var composedType = GetOriginalComposedType(targetClassType); - string targetClassName = composedType is not null ? TranslateType(composedType) : TranslateTypescriptType(targetClassType); + string targetClassName = TranslateTypescriptType(composedType ?? targetClassType); var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; if (GetTypescriptTypeString(targetClassType, currentElement, false, writer) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass && GetFactoryMethod(definitionClass, resultName) is { } factoryMethod) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index dc5ee29954..86ce068b01 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -31,14 +31,15 @@ public void GetParentOfTypeOrNull_ShouldReturnParent_WhenParentOfTypeExists() [Fact] public void TranslateType_ThrowsArgumentNullException_WhenComposedTypeIsNull() { - Assert.Throws(() => TypeScriptConventionService.TranslateType(null)); + var result = TypeScriptConventionService.TranslateTypescriptType(null); + Assert.Equal(result, TypeScriptConventionService.TYPE_OBJECT); } [Fact] public void TranslateType_ReturnsCorrectTranslation_WhenComposedTypeIsNotNull() { var composedType = new CodeUnionType { Name = "test" }; - var result = TypeScriptConventionService.TranslateType(composedType); + var result = TypeScriptConventionService.TranslateTypescriptType(composedType); Assert.Equal("Test", result); } } From e4b1f2da1754c1c0660e8284fa84341419d98c58 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 27 Jun 2024 12:59:47 +0300 Subject: [PATCH 067/117] format code --- .../Writers/TypeScript/TypeScriptConventionServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index 86ce068b01..6a534f4bef 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -32,7 +32,7 @@ public void GetParentOfTypeOrNull_ShouldReturnParent_WhenParentOfTypeExists() public void TranslateType_ThrowsArgumentNullException_WhenComposedTypeIsNull() { var result = TypeScriptConventionService.TranslateTypescriptType(null); - Assert.Equal(result, TypeScriptConventionService.TYPE_OBJECT); + Assert.Equal(TypeScriptConventionService.TYPE_OBJECT, result); } [Fact] From 4787e3bc2f852f3d28f87603c6788ab77899e053 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 27 Jun 2024 13:14:51 +0300 Subject: [PATCH 068/117] fix sonar warnings --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 08f4f6e96e..885724bc95 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -392,12 +392,6 @@ private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface co return method; } - private static CodeMethod CreateDeserializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function) - { - var method = CodeMethod.FromCodeFunctionAndInterface(codeInterface, function, GetComposedTypeMethodKind(function)); - return method; - } - private static CodeMethodKind GetComposedTypeMethodKind(CodeFunction function) { return function.OriginalLocalMethod.Kind switch From 881f0ddda115fd145038caa604d101a9c135338d Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 28 Jun 2024 17:25:40 +0300 Subject: [PATCH 069/117] search for factory function using namespace only --- .../TypeScript/TypeScriptConventionService.cs | 16 +------------- .../TypeScriptConventionServiceTests.cs | 21 ------------------- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index c0aa01b749..b7cfdd5889 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -287,23 +287,9 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); } - public static T? GetParentOfTypeOrNull(CodeElement codeElement) where T : class - { - ArgumentNullException.ThrowIfNull(codeElement); - try - { - return codeElement.GetImmediateParentOfType(); - } - catch (InvalidOperationException) - { - return null; - } - } - private static CodeFunction? GetFactoryMethod(CodeInterface definitionClass, string factoryMethodName) { - return GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName) - ?? GetParentOfTypeOrNull(definitionClass)?.FindChildByName(factoryMethodName); + return definitionClass.GetImmediateParentOfType(definitionClass)?.FindChildByName(factoryMethodName); } public string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index 6a534f4bef..df1f9a6af1 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -7,27 +7,6 @@ namespace Kiota.Builder.Tests.Writers.TypeScript; public class TypeScriptConventionServiceTests { - [Fact] - public void GetParentOfTypeOrNull_ShouldThrowArgumentNullException_WhenCodeElementIsNull() - { - Assert.Throws(() => TypeScriptConventionService.GetParentOfTypeOrNull(null)); - } - - [Fact] - public void GetParentOfTypeOrNull_ShouldReturnNull_WhenNoParentOfTypeExists() - { - var codeElement = new CodeProperty { Name = "Test Property", Type = new CodeType { Name = "test" } }; - Assert.Null(TypeScriptConventionService.GetParentOfTypeOrNull(codeElement)); - } - - [Fact] - public void GetParentOfTypeOrNull_ShouldReturnParent_WhenParentOfTypeExists() - { - var parent = new CodeClass(); - var codeElement = new CodeProperty { Name = "Test Property", Parent = parent, Type = new CodeType { Name = "test" } }; - Assert.Equal(parent, TypeScriptConventionService.GetParentOfTypeOrNull(codeElement)); - } - [Fact] public void TranslateType_ThrowsArgumentNullException_WhenComposedTypeIsNull() { From fe5ac630c75202f9fad5369a9d1a493e1bfcda6d Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 1 Jul 2024 10:44:01 +0300 Subject: [PATCH 070/117] use a more meaningful method name --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index cb61d6ddea..d4ce20ec99 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -361,14 +361,14 @@ private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter wr writer.WriteLine($"writer.writeAdditionalData({codeInterface.Name.ToFirstCharacterLowerCase()}.{additionalDataProperty.Name.ToFirstCharacterLowerCase()});"); } - private static bool IsCodePropertyCollectionOfEnum(CodeProperty property) + private static bool IsCollectionOfEnum(CodeProperty property) { - return property.Type is CodeType cType && cType.IsCollection && cType.TypeDefinition is CodeEnum; + return property.Type is CodeType codeType && codeType.IsCollection && codeType.TypeDefinition is CodeEnum; } private void WritePropertySerializer(string modelParamName, CodeProperty codeProperty, LanguageWriter writer, CodeFunction codeFunction) { - var isCollectionOfEnum = IsCodePropertyCollectionOfEnum(codeProperty); + var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); From 7a662c4e56587c04c439ce15cd474e374a62abe5 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 1 Jul 2024 13:56:41 +0300 Subject: [PATCH 071/117] appply pr suggestion --- .../Writers/TypeScript/TypeScriptConventionService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index b7cfdd5889..597eb0586f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -125,7 +125,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ return GetComposedTypeTypeString(composedType, targetElement, collectionSuffix); } - CodeTypeBase codeType = composedType is not null ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : code; + CodeTypeBase codeType = composedType is not null ? new CodeType() { TypeDefinition = composedType } : code; if (codeType is not CodeType currentType) { @@ -306,9 +306,9 @@ public string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod met return (currentType.TypeDefinition, isCollection, propertyType) switch { (CodeEnum currentEnum, _, _) when currentEnum.CodeEnumObject is not null => $"{(currentEnum.Flags || isCollection ? "getCollectionOfEnumValues" : "getEnumValue")}<{currentEnum.Name.ToFirstCharacterUpperCase()}>({currentEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()})", - (_, _, string prop) when StreamTypeName.Equals(prop, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", - (_, true, string prop) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{prop}>()", - (_, true, string prop) => $"getCollectionOfObjectValues<{prop.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(_codeType, method)})", + (_, _, _) when StreamTypeName.Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "getByteArrayValue", + (_, true, _) when currentType.TypeDefinition is null => $"getCollectionOfPrimitiveValues<{propertyType}>()", + (_, true, _) => $"getCollectionOfObjectValues<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(_codeType, method)})", _ => GetDeserializationMethodNameForPrimitiveOrObject(_codeType, propertyType, method) }; } From 8fefc3b3773a14c798fe80930f827f18ca1a19b1 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 12:26:47 +0300 Subject: [PATCH 072/117] refactor code remove new enum values --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 3 - .../Refiners/TypeScriptRefiner.cs | 93 +++++-------------- .../Writers/TypeScript/CodeFunctionWriter.cs | 39 +++++--- .../TypeScript/CodeFunctionWriterTests.cs | 8 +- 4 files changed, 50 insertions(+), 93 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 8375416530..897178cb1e 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -15,9 +15,6 @@ public enum CodeMethodKind RequestGenerator, Serializer, Deserializer, - ComposedTypeFactory, - ComposedTypeDeserializer, - ComposedTypeSerializer, Constructor, Getter, Setter, diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 885724bc95..040d78909c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -274,17 +274,17 @@ private static IEnumerable GetSerializationAndFactoryFunctions(Cod return codeNamespace.GetChildElements(true) .OfType() .Where(codeFunction => - IsRelevantDeserializerOrSerializer(codeFunction, codeInterface) || - IsRelevantFactory(codeFunction, codeInterface, codeNamespace)); + IsDeserializerOrSerializerFuntion(codeFunction, codeInterface) || + IsFactoryFuntion(codeFunction, codeInterface, codeNamespace)); } - private static bool IsRelevantDeserializerOrSerializer(CodeFunction codeFunction, CodeInterface codeInterface) + private static bool IsDeserializerOrSerializerFuntion(CodeFunction codeFunction, CodeInterface codeInterface) { return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Serializer && codeFunction.OriginalLocalMethod.Parameters.Any(x => x.Type is CodeType codeType && codeType.TypeDefinition == codeInterface); } - private static bool IsRelevantFactory(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) + private static bool IsFactoryFuntion(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) { return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && @@ -299,9 +299,9 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac composedType }; - ReplaceFactoryMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceSerializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); - ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); + ReplaceFactoryMethodForComposedType(composedType, children); + ReplaceSerializerMethodForComposedType(composedType, children); + ReplaceDeserializerMethodForComposedType(composedType, children); return children; } @@ -311,96 +311,45 @@ private static List GetCodeFileElementsForComposedType(CodeInterfac return elements.OfType().FirstOrDefault(function => function.OriginalLocalMethod.IsOfKind(kind)); } - private static void RemoveOldCodeFunctionAndAddNewOne(List children, CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction oldCodeFunction, CodeFunction newCodeFunction) - { - children.Remove(oldCodeFunction); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, oldCodeFunction); - children.Add(newCodeFunction); - } - private static void RemoveUnusedDeserializerImport(List children, CodeFunction factoryFunction) { if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is { } deserializerMethod) factoryFunction.RemoveUsingsByDeclarationName(deserializerMethod.Name); } - private static void ReplaceFactoryMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + private static void ReplaceFactoryMethodForComposedType(CodeComposedTypeBase composedType, List children) { - if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } factoryMethod) return; - - var method = CreateFactoryMethodForComposedType(codeInterface, composedType, factoryMethod); - var factoryFunction = new CodeFunction(method) { Name = method.Name, Parent = codeInterface.OriginalClass }; - factoryFunction.AddUsing(factoryMethod.Usings.ToArray()); + if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; - RemoveOldCodeFunctionAndAddNewOne(children, codeInterface, codeNamespace, factoryMethod, factoryFunction); + if (composedType is not null && IsComposedOfPrimitives(composedType)) + function.OriginalLocalMethod.ReturnType = composedType; // Remove the deserializer import statement if its not being used - if (composedType is CodeUnionType || IsComposedOfPrimitives(composedType)) + if (composedType is CodeUnionType || composedType is not null && IsComposedOfPrimitives(composedType)) { - RemoveUnusedDeserializerImport(children, factoryFunction); + RemoveUnusedDeserializerImport(children, function); } } - private static void ReplaceSerializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase composedType, List children) { if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; - var method = CreateSerializerMethodForComposedType(codeInterface, function, composedType); - var serializerFunction = new CodeFunction(method) { Name = method.Name }; - serializerFunction.AddUsing(function.Usings.ToArray()); - - RemoveOldCodeFunctionAndAddNewOne(children, codeInterface, codeNamespace, function, serializerFunction); + var method = function.OriginalLocalMethod; + // Add the key parameter if the composed type is a union of primitive values + if (IsComposedOfPrimitives(composedType)) + method.AddParameter(CreateKeyParameter()); } - private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) + private static void ReplaceDeserializerMethodForComposedType(CodeComposedTypeBase composedType, List children) { if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; // For code union Deserializer is not required, however its needed for Object Intersection types - if (composedType is CodeIntersectionType && !IsComposedOfPrimitives(composedType)) + if (composedType is not CodeIntersectionType || IsComposedOfPrimitives(composedType)) { - var method = CodeMethod.FromCodeFunctionAndInterface(codeInterface, deserializerMethod, GetComposedTypeMethodKind(deserializerMethod)); - var deserializerFunction = new CodeFunction(method) { Name = method.Name }; - deserializerFunction.AddUsing(deserializerMethod.Usings.ToArray()); - children.Add(deserializerFunction); + children.Remove(deserializerMethod); } - - children.Remove(deserializerMethod); - RemoveChildElementFromInterfaceAndNamespace(codeInterface, codeNamespace, deserializerMethod); - } - - private static void RemoveChildElementFromInterfaceAndNamespace(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeFunction function) - { - codeInterface.RemoveChildElement(function); - codeNamespace.RemoveChildElement(function); - } - - private static CodeMethod CreateFactoryMethodForComposedType(CodeInterface codeInterface, CodeComposedTypeBase composedType, CodeFunction function) - { - var method = CodeMethod.FromCodeFunctionAndInterface(codeInterface, function, GetComposedTypeMethodKind(function)); - if (composedType is not null && IsComposedOfPrimitives(composedType)) - method.ReturnType = composedType; - return method; - } - - private static CodeMethod CreateSerializerMethodForComposedType(CodeInterface codeInterface, CodeFunction function, CodeComposedTypeBase composedType) - { - var method = CodeMethod.FromCodeFunctionAndInterface(codeInterface, function, GetComposedTypeMethodKind(function)); - // Add the key parameter if the composed type is a union of primitive values - if (IsComposedOfPrimitives(composedType)) - method.AddParameter(CreateKeyParameter()); - return method; - } - - private static CodeMethodKind GetComposedTypeMethodKind(CodeFunction function) - { - return function.OriginalLocalMethod.Kind switch - { - CodeMethodKind.Factory => CodeMethodKind.ComposedTypeFactory, - CodeMethodKind.Serializer => CodeMethodKind.ComposedTypeSerializer, - CodeMethodKind.Deserializer => CodeMethodKind.ComposedTypeDeserializer, - _ => throw new InvalidOperationException($"Unsupported method type :: {function.OriginalLocalMethod.Kind}") - }; } private static CodeParameter CreateKeyParameter() diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index d4ce20ec99..a436de8b1c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -25,9 +25,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; - var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); + var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is { } composedType && IsComposedOfPrimitives(composedType); - var returnType = codeMethod.Kind is CodeMethodKind.Factory || (codeMethod.Kind is CodeMethodKind.ComposedTypeFactory && !isComposedOfPrimitives) ? + var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : GetTypescriptTypeString(codeMethod.ReturnType, codeElement, inlineComposedTypeString: true); var isVoid = "void".EqualsIgnoreCase(returnType); @@ -45,18 +45,11 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w WriteSerializerFunction(codeElement, writer); break; case CodeMethodKind.Factory: - case CodeMethodKind.ComposedTypeFactory: WriteDiscriminatorFunction(codeElement, writer); break; case CodeMethodKind.ClientConstructor: WriteApiConstructorBody(parentFile, codeMethod, writer); break; - case CodeMethodKind.ComposedTypeSerializer: - WriteComposedTypeSerializer(codeElement, writer); - break; - case CodeMethodKind.ComposedTypeDeserializer: - WriteComposedTypeDeserializer(codeElement, writer); - break; default: throw new InvalidOperationException("Invalid code method kind"); } } @@ -73,10 +66,14 @@ private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedTy writer.WriteLine("return undefined;"); } - private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer) + private static CodeParameter? GetComposedTypeParameter(CodeFunction codeElement) { - var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); + return codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); + } + private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer, CodeParameter composedParam) + { + if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); @@ -88,10 +85,8 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri writer.CloseBlock(); } - private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer) + private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer, CodeParameter composedParam) { - var composedParam = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); - if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; if (IsComposedOfPrimitives(composedType)) @@ -338,6 +333,14 @@ private static string CreateSerializationFunctionNameFromType(string returnType, private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) { + // Determine if the function serializes a composed type + var composedParam = GetComposedTypeParameter(codeElement); + if (composedParam is not null) + { + WriteComposedTypeSerializer(codeElement, writer, composedParam); + return; + } + if (codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(static x => x.Type is CodeType type && type.TypeDefinition is CodeInterface) is not { Type: CodeType @@ -452,6 +455,14 @@ _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalI private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { + // handle the composed type deserializer differently + var composedParam = GetComposedTypeParameter(codeFunction); + if (composedParam is not null) + { + WriteComposedTypeDeserializer(codeFunction, writer, composedParam); + return; + } + var param = codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault(); if (param?.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) { diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 26e5e5fd3f..e560668bbf 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1212,7 +1212,7 @@ public async Task Writes_UnionOfObjects_FactoryMethod() Assert.NotNull(modelCodeFile); // Test Serializer function - var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeFactory).FirstOrDefault(); + var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).FirstOrDefault(); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); @@ -1335,7 +1335,7 @@ public async Task Writes_CodeIntersectionType_FactoryMethod() Assert.NotNull(modelCodeFile); // Test Factory Function - var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeFactory).FirstOrDefault(); + var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).FirstOrDefault(); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); @@ -1373,7 +1373,7 @@ public async Task Writes_CodeIntersectionType_DeserializerFunctions() Assert.NotNull(modelCodeFile); // Test Deserializer function - var deserializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeDeserializer).FirstOrDefault(); + var deserializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Deserializer).FirstOrDefault(); Assert.True(deserializerFunction is not null); writer.Write(deserializerFunction); var serializerFunctionStr = tw.ToString(); @@ -1411,7 +1411,7 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() Assert.NotNull(modelCodeFile); // Test Serializer function - var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.ComposedTypeSerializer).FirstOrDefault(); + var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Serializer).FirstOrDefault(); Assert.True(serializerFunction is not null); writer.Write(serializerFunction); var serializerFunctionStr = tw.ToString(); From 1de703daebc0f407d54b930d770ab9b152aaa3fe Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 12:37:41 +0300 Subject: [PATCH 073/117] format code --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 8 ++++---- .../Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 040d78909c..360121e2db 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -274,17 +274,17 @@ private static IEnumerable GetSerializationAndFactoryFunctions(Cod return codeNamespace.GetChildElements(true) .OfType() .Where(codeFunction => - IsDeserializerOrSerializerFuntion(codeFunction, codeInterface) || - IsFactoryFuntion(codeFunction, codeInterface, codeNamespace)); + IsDeserializerOrSerializerFunction(codeFunction, codeInterface) || + IsFactoryFunction(codeFunction, codeInterface, codeNamespace)); } - private static bool IsDeserializerOrSerializerFuntion(CodeFunction codeFunction, CodeInterface codeInterface) + private static bool IsDeserializerOrSerializerFunction(CodeFunction codeFunction, CodeInterface codeInterface) { return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Serializer && codeFunction.OriginalLocalMethod.Parameters.Any(x => x.Type is CodeType codeType && codeType.TypeDefinition == codeInterface); } - private static bool IsFactoryFuntion(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) + private static bool IsFactoryFunction(CodeFunction codeFunction, CodeInterface codeInterface, CodeNamespace codeNamespace) { return codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index a436de8b1c..00d430736b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -27,7 +27,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is { } composedType && IsComposedOfPrimitives(composedType); - var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? + var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : GetTypescriptTypeString(codeMethod.ReturnType, codeElement, inlineComposedTypeString: true); var isVoid = "void".EqualsIgnoreCase(returnType); @@ -73,7 +73,7 @@ private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedTy private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer, CodeParameter composedParam) { - + if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); @@ -457,7 +457,7 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter { // handle the composed type deserializer differently var composedParam = GetComposedTypeParameter(codeFunction); - if (composedParam is not null) + if (composedParam is not null) { WriteComposedTypeDeserializer(codeFunction, writer, composedParam); return; From d5bf8a70b56b2707679332d1cea0ddd60c1b614e Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 12:41:43 +0300 Subject: [PATCH 074/117] remove unused parameters --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 360121e2db..0d8f4ed2e6 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -291,7 +291,7 @@ private static bool IsFactoryFunction(CodeFunction codeFunction, CodeInterface c codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace); } - private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, CodeFunction[] functions) + private static List GetCodeFileElementsForComposedType(CodeComposedTypeBase composedType, CodeFunction[] functions) { var children = new List(functions) { From af806212816258a19b4edd3a9992ea0555a91544 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 12:48:30 +0300 Subject: [PATCH 075/117] fix compilation issue --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 0d8f4ed2e6..af6a6db4c3 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -264,7 +264,7 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && return null; var composedType = GetOriginalComposedType(codeInterface); - var elements = composedType is null ? new List { codeInterface }.Concat(functions) : GetCodeFileElementsForComposedType(codeInterface, codeNamespace, composedType, functions); + var elements = composedType is null ? new List { codeInterface }.Concat(functions) : GetCodeFileElementsForComposedType(composedType, functions); return codeNamespace.TryAddCodeFile(codeInterface.Name, elements.ToArray()); } From 681b900a5039b1975aa81b3d618c14374480f7cb Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 15:13:13 +0300 Subject: [PATCH 076/117] remove unused deserializer --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index af6a6db4c3..dc54669ea0 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -264,7 +264,7 @@ parentNamespace.Parent is CodeNamespace parentLevelNamespace && return null; var composedType = GetOriginalComposedType(codeInterface); - var elements = composedType is null ? new List { codeInterface }.Concat(functions) : GetCodeFileElementsForComposedType(composedType, functions); + var elements = composedType is null ? new List { codeInterface }.Concat(functions) : GetCodeFileElementsForComposedType(codeInterface, codeNamespace, composedType, functions); return codeNamespace.TryAddCodeFile(codeInterface.Name, elements.ToArray()); } @@ -291,7 +291,7 @@ private static bool IsFactoryFunction(CodeFunction codeFunction, CodeInterface c codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace); } - private static List GetCodeFileElementsForComposedType(CodeComposedTypeBase composedType, CodeFunction[] functions) + private static List GetCodeFileElementsForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, CodeFunction[] functions) { var children = new List(functions) { @@ -301,7 +301,7 @@ private static List GetCodeFileElementsForComposedType(CodeComposed ReplaceFactoryMethodForComposedType(composedType, children); ReplaceSerializerMethodForComposedType(composedType, children); - ReplaceDeserializerMethodForComposedType(composedType, children); + ReplaceDeserializerMethodForComposedType(codeInterface, codeNamespace, composedType, children); return children; } @@ -341,7 +341,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase method.AddParameter(CreateKeyParameter()); } - private static void ReplaceDeserializerMethodForComposedType(CodeComposedTypeBase composedType, List children) + private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) { if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; @@ -349,6 +349,8 @@ private static void ReplaceDeserializerMethodForComposedType(CodeComposedTypeBas if (composedType is not CodeIntersectionType || IsComposedOfPrimitives(composedType)) { children.Remove(deserializerMethod); + codeInterface.RemoveChildElement(deserializerMethod); + codeNamespace.RemoveChildElement(deserializerMethod); } } From a17523a1833cb4506e132868673e5dfcc37b2a3b Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 3 Jul 2024 15:29:46 +0300 Subject: [PATCH 077/117] simplify statement --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index dc54669ea0..bae0cc2cf5 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -335,10 +335,9 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase { if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; - var method = function.OriginalLocalMethod; // Add the key parameter if the composed type is a union of primitive values if (IsComposedOfPrimitives(composedType)) - method.AddParameter(CreateKeyParameter()); + function.OriginalLocalMethod.AddParameter(CreateKeyParameter()); } private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) From 02d880b0a67287b18a62b6ba6164bd203f0f769a Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 4 Jul 2024 15:19:43 +0300 Subject: [PATCH 078/117] apply pr review suggestions --- .../Refiners/TypeScriptRefiner.cs | 7 +++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 30 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 579697f670..552a99cb00 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -9,11 +9,10 @@ using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.Refiners; -public class TypeScriptRefiner : CommonLanguageRefiner, ILanguageRefiner +public class TypeScriptRefiner(GenerationConfiguration configuration) : CommonLanguageRefiner(configuration), ILanguageRefiner { public static readonly string BackingStoreEnabledKey = "backingStoreEnabled"; - public TypeScriptRefiner(GenerationConfiguration configuration) : base(configuration) { } public override Task Refine(CodeNamespace generatedCode, CancellationToken cancellationToken) { return Task.Run(() => @@ -21,6 +20,10 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); DeduplicateErrorMappings(generatedCode); RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlConstructor, CodeMethodKind.RawUrlBuilder); + // Invoke the ConvertUnionTypesToWrapper method to maintain a consistent CodeDOM structure. + // Note that in the later stages, specifically within the GenerateModelCodeFile() function, the introduced wrapper interface is disregarded. + // Instead, a ComposedType is created, which has its own writer, along with the associated Factory, Serializer, and Deserializer functions + // that are incorporated into the CodeFile. ConvertUnionTypesToWrapper( generatedCode, _configuration.UsesBackingStore, diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 00d430736b..38cc9d9b3e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -455,7 +455,7 @@ _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalI private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { - // handle the composed type deserializer differently + // handle composed types var composedParam = GetComposedTypeParameter(codeFunction); if (composedParam is not null) { @@ -466,20 +466,28 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter var param = codeFunction.OriginalLocalMethod.Parameters.FirstOrDefault(); if (param?.Type is CodeType codeType && codeType.TypeDefinition is CodeInterface codeInterface) { - var properties = codeInterface.Properties.Where(static x => x.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.BackingStore) && !x.ExistsInBaseType); + WriteDeserializerFunctionProperties(param, codeInterface, codeFunction, writer); + } + else + { + throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); + } + } - writer.StartBlock("return {"); - WriteInheritsBlock(codeInterface, param, writer); - var (primaryErrorMapping, primaryErrorMappingKey) = GetPrimaryErrorMapping(codeFunction, param); + private void WriteDeserializerFunctionProperties(CodeParameter param, CodeInterface codeInterface, CodeFunction codeFunction, LanguageWriter writer) + { + var properties = codeInterface.Properties.Where(static x => x.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.BackingStore) && !x.ExistsInBaseType); - foreach (var otherProp in properties) - { - WritePropertyBlock(otherProp, param, primaryErrorMapping, primaryErrorMappingKey, codeFunction, writer); - } + writer.StartBlock("return {"); + WriteInheritsBlock(codeInterface, param, writer); + var (primaryErrorMapping, primaryErrorMappingKey) = GetPrimaryErrorMapping(codeFunction, param); - writer.CloseBlock(); + foreach (var otherProp in properties) + { + WritePropertyBlock(otherProp, param, primaryErrorMapping, primaryErrorMappingKey, codeFunction, writer); } - else throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); + + writer.CloseBlock(); } private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param, LanguageWriter writer) From 8d1278e754fd93d2df024c13df8d8491d181c10a Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 16 Jul 2024 16:32:29 +0300 Subject: [PATCH 079/117] remove unused parameter --- .../Writers/TypeScript/TypeScriptConventionService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 597eb0586f..b1a38bd650 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -108,10 +108,10 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null) { - return GetTypescriptTypeString(code, targetElement, includeCollectionInformation, writer); + return GetTypescriptTypeString(code, targetElement, includeCollectionInformation); } - public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null, bool inlineComposedTypeString = false) + public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, bool inlineComposedTypeString = false) { ArgumentNullException.ThrowIfNull(code); ArgumentNullException.ThrowIfNull(targetElement); @@ -278,7 +278,7 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem var composedType = GetOriginalComposedType(targetClassType); string targetClassName = TranslateTypescriptType(composedType ?? targetClassType); var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; - if (GetTypescriptTypeString(targetClassType, currentElement, false, writer) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; + if (GetTypescriptTypeString(targetClassType, currentElement, false) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass && GetFactoryMethod(definitionClass, resultName) is { } factoryMethod) { var methodName = GetTypescriptTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); From 04a417870d389608ebbb85f6aaf00cac64471962 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 23 Jul 2024 12:23:16 +0300 Subject: [PATCH 080/117] use | symbol for code intersection in typescript --- .../Writers/TypeScript/CodeFunctionWriter.cs | 4 ++-- .../TypeScript/CodeIntersectionTypeWriter.cs | 6 ++++- .../TypeScript/TypeScriptConventionService.cs | 7 +----- .../TypeScript/CodeFunctionWriterTests.cs | 22 +++++++++---------- .../CodeIntersectionTypeWriterTests.cs | 2 +- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 38cc9d9b3e..2dc8e055be 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -80,7 +80,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri foreach (var mappedType in composedType.Types.ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()}),"); + writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName}),"); } writer.CloseBlock(); } @@ -109,7 +109,7 @@ private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedT foreach (var mappedType in composedType.Types.ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()});"); + writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName});"); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs index be9519356c..69edee087f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeIntersectionTypeWriter.cs @@ -4,8 +4,12 @@ namespace Kiota.Builder.Writers.TypeScript; public class CodeIntersectionTypeWriter(TypeScriptConventionService conventionService) : CodeComposedTypeBaseWriter(conventionService) { + // The `CodeIntersectionType` will utilize the same union symbol `|`, but the methods for serialization and deserialization + // will differ slightly. This is because the `CodeIntersectionType` for `Foo` and `Bar` can encompass both `Foo` and `Bar` + // simultaneously, whereas the `CodeUnion` can only include either `Foo` or `Bar`, but not both at the same time. + public override string TypesDelimiter { - get => "&"; + get => "|"; } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index b1a38bd650..7b3ee71232 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -158,12 +158,7 @@ private static string GetComposedTypeTypeString(CodeComposedTypeBase composedTyp private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) { - return codeComposedTypeBase switch - { - CodeUnionType _ => " | ", - CodeIntersectionType _ => " & ", - _ => throw new InvalidOperationException("unknown composed type"), - }; + return codeComposedTypeBase is CodeUnionType or CodeIntersectionType ? " | " : throw new InvalidOperationException("Unknown composed type"); } private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index e560668bbf..def967162e 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1173,7 +1173,7 @@ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | un */ // Test Factory function - var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null).FirstOrDefault(); + var factoryFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.ReturnType) is not null); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); @@ -1212,7 +1212,7 @@ public async Task Writes_UnionOfObjects_FactoryMethod() Assert.NotNull(modelCodeFile); // Test Serializer function - var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).FirstOrDefault(); + var factoryFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); @@ -1253,7 +1253,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() Assert.NotNull(modelCodeFile); // Test Serializer function - var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null).FirstOrDefault(); + var serializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null); Assert.True(serializerFunction is not null); writer.Write(serializerFunction); var serializerFunctionStr = tw.ToString(); @@ -1294,7 +1294,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() Assert.NotNull(modelCodeFile); // Test Serializer function - var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null).FirstOrDefault(); + var serializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null); Assert.True(serializerFunction is not null); writer.Write(serializerFunction); var serializerFunctionStr = tw.ToString(); @@ -1335,7 +1335,7 @@ public async Task Writes_CodeIntersectionType_FactoryMethod() Assert.NotNull(modelCodeFile); // Test Factory Function - var factoryFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).FirstOrDefault(); + var factoryFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory); Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); @@ -1373,12 +1373,12 @@ public async Task Writes_CodeIntersectionType_DeserializerFunctions() Assert.NotNull(modelCodeFile); // Test Deserializer function - var deserializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Deserializer).FirstOrDefault(); + var deserializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Deserializer); Assert.True(deserializerFunction is not null); writer.Write(deserializerFunction); var serializerFunctionStr = tw.ToString(); - Assert.Contains("...deserializeIntoBar(fooBar),", serializerFunctionStr); - Assert.Contains("...deserializeIntoFoo(fooBar),", serializerFunctionStr); + Assert.Contains("...deserializeIntoBar(fooBar as Bar),", serializerFunctionStr); + Assert.Contains("...deserializeIntoFoo(fooBar as Foo),", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } @@ -1411,12 +1411,12 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() Assert.NotNull(modelCodeFile); // Test Serializer function - var serializerFunction = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Serializer).FirstOrDefault(); + var serializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Serializer); Assert.True(serializerFunction is not null); writer.Write(serializerFunction); var serializerFunctionStr = tw.ToString(); - Assert.Contains("serializeBar(writer, fooBar);", serializerFunctionStr); - Assert.Contains("serializeFoo(writer, fooBar);", serializerFunctionStr); + Assert.Contains("serializeBar(writer, fooBar as Bar);", serializerFunctionStr); + Assert.Contains("serializeFoo(writer, fooBar as Foo);", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs index 0b8622194b..1b6468a1c2 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeIntersectionTypeWriterTests.cs @@ -61,6 +61,6 @@ public void WriteCodeElement_ShouldWriteCorrectOutput_WhenTypesIsNotEmpty() codeElementWriter.WriteCodeElement(composedType, writer); var result = tw.ToString(); - Assert.Contains("export type Test = Type1 & Type2;", result); + Assert.Contains("export type Test = Type1 | Type2;", result); } } From 33becc4a601c007e5b36231d68110f493131430d Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 23 Jul 2024 15:29:59 +0300 Subject: [PATCH 081/117] move composed-type utility method to extensions --- .../CodeComposedTypeBaseExtensions.cs | 13 +++++ .../Refiners/TypeScriptRefiner.cs | 8 +-- .../Writers/TypeScript/CodeFunctionWriter.cs | 14 ++--- .../TypeScript/TypeScriptConventionService.cs | 9 +-- .../CodeComposedTypeBaseExtensionsTests.cs | 57 +++++++++++++++++++ 5 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs create mode 100644 tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs diff --git a/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs b/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs new file mode 100644 index 0000000000..8441765801 --- /dev/null +++ b/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs @@ -0,0 +1,13 @@ +using System.Linq; +using Kiota.Builder.CodeDOM; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; + +namespace Kiota.Builder.Extensions; + +public static class CodeComposedTypeBaseExtensions +{ + public static bool IsComposedOfPrimitives(this CodeComposedTypeBase composedType) + { + return composedType?.Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, composedType))) ?? false; + } +} diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 552a99cb00..f3a91cae7d 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -324,11 +324,11 @@ private static void ReplaceFactoryMethodForComposedType(CodeComposedTypeBase com { if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; - if (composedType is not null && IsComposedOfPrimitives(composedType)) + if (composedType is not null && composedType.IsComposedOfPrimitives()) function.OriginalLocalMethod.ReturnType = composedType; // Remove the deserializer import statement if its not being used - if (composedType is CodeUnionType || composedType is not null && IsComposedOfPrimitives(composedType)) + if (composedType is CodeUnionType || composedType is not null && composedType.IsComposedOfPrimitives()) { RemoveUnusedDeserializerImport(children, function); } @@ -339,7 +339,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; // Add the key parameter if the composed type is a union of primitive values - if (IsComposedOfPrimitives(composedType)) + if (composedType.IsComposedOfPrimitives()) function.OriginalLocalMethod.AddParameter(CreateKeyParameter()); } @@ -348,7 +348,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; // For code union Deserializer is not required, however its needed for Object Intersection types - if (composedType is not CodeIntersectionType || IsComposedOfPrimitives(composedType)) + if (composedType is not CodeIntersectionType || composedType.IsComposedOfPrimitives()) { children.Remove(deserializerMethod); codeInterface.RemoveChildElement(deserializerMethod); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 2dc8e055be..f0e843e6a6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -25,7 +25,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; - var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is { } composedType && IsComposedOfPrimitives(composedType); + var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is { } composedType && composedType.IsComposedOfPrimitives(); var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : @@ -89,7 +89,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite { if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; - if (IsComposedOfPrimitives(composedType)) + if (composedType.IsComposedOfPrimitives()) { WriteComposedTypeSerializationForPrimitives(composedType, composedParam, codeElement, writer); return; @@ -225,7 +225,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when IsComposedOfPrimitives(type): + case CodeComposedTypeBase type when type.IsComposedOfPrimitives(): WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: @@ -292,7 +292,7 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, CodeParameter? parseNodeParameter) { - if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is { } composedType && IsComposedOfPrimitives(composedType) && parseNodeParameter is not null) + if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is { } composedType && composedType.IsComposedOfPrimitives() && parseNodeParameter is not null) { return $"({parseNodeParameter.Name.ToFirstCharacterLowerCase()})"; } @@ -392,7 +392,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { writer.WriteLine($"if({modelParamName}.{codePropertyName})"); } - if (composedType is not null && IsComposedOfPrimitives(composedType)) + if (composedType is not null && composedType.IsComposedOfPrimitives()) writer.WriteLine($"{serializationName}(writer, \"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); else writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); @@ -406,7 +406,7 @@ public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod m ArgumentNullException.ThrowIfNull(method); var composedType = GetOriginalComposedType(propertyType); - if (composedType is not null && IsComposedOfPrimitives(composedType)) + if (composedType is not null && composedType.IsComposedOfPrimitives()) { return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; } @@ -518,7 +518,7 @@ private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, str var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; if (otherProp.Kind is CodePropertyKind.BackingStore) writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); - else if (GetOriginalComposedType(otherProp.Type) is { } composedType && IsComposedOfPrimitives(composedType)) + else if (GetOriginalComposedType(otherProp.Type) is { } composedType && composedType.IsComposedOfPrimitives()) { writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 7b3ee71232..f42d4bfcf9 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -5,7 +5,6 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -using Kiota.Builder.Refiners; using static Kiota.Builder.CodeDOM.CodeTypeBase; using static Kiota.Builder.Refiners.TypeScriptRefiner; @@ -81,17 +80,11 @@ public override string GetAccessModifier(AccessModifier access) }; } - public static bool IsComposedOfPrimitives(CodeComposedTypeBase composedType) - { - // Primitive values don't have a discriminator property so it should be handled differently if any of the values is primitive - return composedType?.Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, composedType))) ?? false; - } - public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) { ArgumentNullException.ThrowIfNull(parameter); var paramType = GetTypescriptTypeString(parameter.Type, targetElement, inlineComposedTypeString: true); - var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && IsComposedOfPrimitives(composedType); + var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { (false, CodeParameterKind.DeserializationTarget, false) => $" = {parameter.DefaultValue}", diff --git a/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs new file mode 100644 index 0000000000..564e77eb2f --- /dev/null +++ b/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs @@ -0,0 +1,57 @@ +using System.ComponentModel.Design.Serialization; +using System.IO; +using System.Linq; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +using Xunit; + +namespace Kiota.Builder.Tests.Extensions; +public class CodeComposedTypeBaseExtensionsTests +{ + private readonly CodeType currentType; + private const string TypeName = "SomeType"; + public CodeComposedTypeBaseExtensionsTests() + { + currentType = new() + { + Name = TypeName + }; + var root = CodeNamespace.InitRootNamespace(); + var parentClass = root.AddClass(new CodeClass + { + Name = "ParentClass" + }).First(); + currentType.Parent = parentClass; + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); + Assert.True(composedType.IsComposedOfPrimitives()); + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + CodeClass td = new CodeClass + { + Name = "SomeClass" + }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + Assert.False(composedType.IsComposedOfPrimitives()); + } +} From 4dec28dbf80bd43b14e2781306c458e31b6044fc Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 23 Jul 2024 16:11:14 +0300 Subject: [PATCH 082/117] remove CodeComposedTypeBase extension class --- .../CodeDOM/CodeComposedTypeBase.cs | 2 + .../CodeComposedTypeBaseExtensions.cs | 13 ----- .../CodeDOM/CodeComposedTypeBaseTests.cs | 55 ++++++++++++++++++ .../CodeComposedTypeBaseExtensionsTests.cs | 57 ------------------- 4 files changed, 57 insertions(+), 70 deletions(-) delete mode 100644 src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs create mode 100644 tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs delete mode 100644 tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index aa600049a0..03d5020f6c 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; namespace Kiota.Builder.CodeDOM; /// @@ -71,4 +72,5 @@ public DeprecationInformation? Deprecation get; set; } + public bool IsComposedOfPrimitives() => Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); } diff --git a/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs b/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs deleted file mode 100644 index 8441765801..0000000000 --- a/src/Kiota.Builder/Extensions/CodeComposedTypeBaseExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Linq; -using Kiota.Builder.CodeDOM; -using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; - -namespace Kiota.Builder.Extensions; - -public static class CodeComposedTypeBaseExtensions -{ - public static bool IsComposedOfPrimitives(this CodeComposedTypeBase composedType) - { - return composedType?.Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, composedType))) ?? false; - } -} diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs new file mode 100644 index 0000000000..1ae7bc711c --- /dev/null +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Kiota.Builder.CodeDOM; +using Xunit; + +namespace Kiota.Builder.Tests.CodeDOM +{ + public class CodeComposedTypeBaseTests + { + private readonly CodeType currentType; + private const string TypeName = "SomeType"; + public CodeComposedTypeBaseTests() + { + currentType = new() + { + Name = TypeName + }; + var root = CodeNamespace.InitRootNamespace(); + var parentClass = root.AddClass(new CodeClass + { + Name = "ParentClass" + }).First(); + currentType.Parent = parentClass; + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); + Assert.True(composedType.IsComposedOfPrimitives()); + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + CodeClass td = new CodeClass + { + Name = "SomeClass" + }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + Assert.False(composedType.IsComposedOfPrimitives()); + } + } +} diff --git a/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs deleted file mode 100644 index 564e77eb2f..0000000000 --- a/tests/Kiota.Builder.Tests/Extensions/CodeComposedTypeBaseExtensionsTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.ComponentModel.Design.Serialization; -using System.IO; -using System.Linq; -using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; - -using Xunit; - -namespace Kiota.Builder.Tests.Extensions; -public class CodeComposedTypeBaseExtensionsTests -{ - private readonly CodeType currentType; - private const string TypeName = "SomeType"; - public CodeComposedTypeBaseExtensionsTests() - { - currentType = new() - { - Name = TypeName - }; - var root = CodeNamespace.InitRootNamespace(); - var parentClass = root.AddClass(new CodeClass - { - Name = "ParentClass" - }).First(); - currentType.Parent = parentClass; - } - - [Fact] - public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); - Assert.True(composedType.IsComposedOfPrimitives()); - } - - [Fact] - public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - CodeClass td = new CodeClass - { - Name = "SomeClass" - }; - composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); - Assert.False(composedType.IsComposedOfPrimitives()); - } -} From 515c8724c0aba266d1ae6fd0505afb953e766bc9 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 23 Jul 2024 17:29:17 +0300 Subject: [PATCH 083/117] use Nullish Coalescing Operator ?? over logical or || --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 5 +---- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index f0e843e6a6..b8041c3ae1 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -58,12 +58,9 @@ private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedTy { ArgumentNullException.ThrowIfNull(parseNodeParameter); var parseNodeParameterName = parseNodeParameter.Name.ToFirstCharacterLowerCase(); - writer.StartBlock($"if ({parseNodeParameterName}) {{"); - string getPrimitiveValueString = string.Join(" || ", composedType.Types.Select(x => $"{parseNodeParameterName}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + string getPrimitiveValueString = string.Join(" ?? ", composedType.Types.Select(x => $"{parseNodeParameterName}?." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); writer.WriteLine($"return {getPrimitiveValueString};"); - writer.CloseBlock(); - writer.WriteLine("return undefined;"); } private static CodeParameter? GetComposedTypeParameter(CodeFunction codeElement) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index def967162e..cfc8a7c224 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1177,9 +1177,7 @@ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | un Assert.True(factoryFunction is not null); writer.Write(factoryFunction); var result = tw.ToString(); - Assert.Contains("if (parseNode) {", result); - Assert.Contains("return parseNode.getNumberValue() || parseNode.getStringValue();", result); - Assert.Contains("return undefined;", result); + Assert.Contains("return parseNode?.getNumberValue() ?? parseNode?.getStringValue();", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } From 6cd579ddbe49001de467047d18a401f8e74e579c Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 29 Jul 2024 18:08:18 +0300 Subject: [PATCH 084/117] handle edge cases for composed types - Union of objects and primitive types without discriminator property --- .../CodeDOM/CodeComposedTypeBase.cs | 20 ++ .../Refiners/TypeScriptRefiner.cs | 14 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 239 ++++++++++++------ .../CodeDOM/CodeComposedTypeBaseTests.cs | 81 ++++++ .../TypeScript/CodeFunctionWriterTests.cs | 8 +- 5 files changed, 277 insertions(+), 85 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index 03d5020f6c..4b3c4ea040 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -73,4 +73,24 @@ public DeprecationInformation? Deprecation set; } public bool IsComposedOfPrimitives() => Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); + public bool IsComposedOfObjectsAndPrimitives() + { + // Count the number of primitives in Types + int primitiveCount = Types.Count(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); + + // If the number of primitives is less than the total count, it means the rest are objects + return primitiveCount > 0 && primitiveCount < Types.Count(); + } + + public IEnumerable GetPrimitiveTypes() + { + // Return only the primitive types from the Types collection + return Types.Where(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); + } + + public IEnumerable GetNonPrimitiveTypes() + { + // Return only the non primitive types from the Types collection + return Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, this))); + } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index f3a91cae7d..f31ae698c8 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -322,14 +322,12 @@ private static void RemoveUnusedDeserializerImport(List children, C private static void ReplaceFactoryMethodForComposedType(CodeComposedTypeBase composedType, List children) { - if (FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; + if (composedType is null || FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; - if (composedType is not null && composedType.IsComposedOfPrimitives()) - function.OriginalLocalMethod.ReturnType = composedType; - - // Remove the deserializer import statement if its not being used - if (composedType is CodeUnionType || composedType is not null && composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives()) { + function.OriginalLocalMethod.ReturnType = composedType; + // Remove the deserializer import statement if its not being used RemoveUnusedDeserializerImport(children, function); } } @@ -347,8 +345,8 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI { if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; - // For code union Deserializer is not required, however its needed for Object Intersection types - if (composedType is not CodeIntersectionType || composedType.IsComposedOfPrimitives()) + // Deserializer function is not required for primitive values + if (composedType.IsComposedOfPrimitives()) { children.Remove(deserializerMethod); codeInterface.RemoveChildElement(deserializerMethod); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index b8041c3ae1..956ff43df7 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -1,10 +1,9 @@ - - -using System; +using System; using System.Collections.Generic; using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.Writers.Go; using static Kiota.Builder.Refiners.TypeScriptRefiner; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; @@ -24,8 +23,8 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w if (codeElement.Parent is not CodeFile parentFile) throw new InvalidOperationException("the parent of a function should be a file"); var codeMethod = codeElement.OriginalLocalMethod; - - var isComposedOfPrimitives = GetOriginalComposedType(codeMethod.ReturnType) is { } composedType && composedType.IsComposedOfPrimitives(); + var composedType = GetOriginalComposedType(codeMethod.ReturnType); + var isComposedOfPrimitives = composedType is not null && composedType.IsComposedOfPrimitives(); var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : @@ -45,7 +44,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w WriteSerializerFunction(codeElement, writer); break; case CodeMethodKind.Factory: - WriteDiscriminatorFunction(codeElement, writer); + WriteFactoryMethod(codeElement, writer); break; case CodeMethodKind.ClientConstructor: WriteApiConstructorBody(parentFile, codeMethod, writer); @@ -54,13 +53,23 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } + private string GetReturnTypeForPrimitiveComposedTypes(CodeComposedTypeBase? composedType) + { + if (composedType == null) return string.Empty; + return " | " + string.Join(" | ", composedType.GetPrimitiveTypes().Select(x => conventions.GetTypeString(x, composedType, false))); + } + + private string GetSerializationMethodsForPrimitiveUnionTypes(CodeComposedTypeBase composedType, string parseNodeParameterName, CodeFunction codeElement, bool nodeParameterCanBeNull = true) + { + var optionalChainingSymbol = nodeParameterCanBeNull ? "?" : string.Empty; + return string.Join(" ?? ", composedType.GetPrimitiveTypes().Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + } + private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) { ArgumentNullException.ThrowIfNull(parseNodeParameter); - var parseNodeParameterName = parseNodeParameter.Name.ToFirstCharacterLowerCase(); - - string getPrimitiveValueString = string.Join(" ?? ", composedType.Types.Select(x => $"{parseNodeParameterName}?." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); - writer.WriteLine($"return {getPrimitiveValueString};"); + string primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, parseNodeParameter.Name.ToFirstCharacterLowerCase(), codeElement); + writer.WriteLine($"return {primitiveValuesUnionString};"); } private static CodeParameter? GetComposedTypeParameter(CodeFunction codeElement) @@ -74,7 +83,8 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); - foreach (var mappedType in composedType.Types.ToArray()) + // Serialization/Deserialization functions can be called for object types only + foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName}),"); @@ -88,36 +98,37 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite if (composedType.IsComposedOfPrimitives()) { - WriteComposedTypeSerializationForPrimitives(composedType, composedParam, codeElement, writer); + WriteSerializationFunctionForTypeComposedOfPrimitives(composedType, composedParam, codeElement, writer); return; } if (composedType is CodeIntersectionType) { - WriteComposedTypeSerializationForCodeIntersectionType(composedType, composedParam, codeElement, writer); + WriteSerializationFunctionForCodeIntersectionType(composedType, composedParam, codeElement, writer); return; } - WriteDefaultComposedTypeSerialization(composedParam, codeElement, writer); + WriteSerializationFunctionForCodeUnionTypes(composedType, composedParam, codeElement, writer); } - private void WriteComposedTypeSerializationForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) + private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { - foreach (var mappedType in composedType.Types.ToArray()) + // Serialization/Deserialization functions can be called for object types only + foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName});"); } } - private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + private void WriteSerializationFunctionForCodeUnionTypes(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; var discriminatorPropertyName = discriminatorInfo.DiscriminatorPropertyName; if (string.IsNullOrEmpty(discriminatorPropertyName)) { - WriteMissingDiscriminatorPropertyComment(composedParam, codeElement, writer); + WriteBruteForceSerializationFunctionForCodeUnionType(composedType, composedParam, codeElement, writer); return; } @@ -126,12 +137,25 @@ private void WriteDefaultComposedTypeSerialization(CodeParameter composedParam, WriteDiscriminatorSwitchBlock(discriminatorInfo, paramName, codeElement, writer); } - private static void WriteMissingDiscriminatorPropertyComment(CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) + /// + /// Writes the brute-force serialization function for a union type. + /// + /// The composed type representing the union. + /// The parameter associated with the composed type. + /// The function code element where serialization is performed. + /// The language writer used to generate the code. + /// + /// This method handles serialization for union types when the discriminator property is missing. + /// In the absence of a discriminator, all possible types in the union are serialized. For example, + /// a Pet union defined as Cat | Dog would result in the serialization of both Cat and Dog types. + /// It delegates the task to the method responsible for intersection types, treating the union + /// similarly to an intersection in this context. + /// + private void WriteBruteForceSerializationFunctionForCodeUnionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { - var typeString = GetTypescriptTypeString(composedParam.Type, codeElement, inlineComposedTypeString: true); - var comment = $"The composed parameter '{composedParam.Name}' consists of {typeString}. However, it lacks a discriminator property, which is necessary for proper type differentiation. Please update the OpenAPI specification to include a discriminator property to ensure correct method generation."; - writer.WriteLine($"// {comment}"); - writer.WriteLine("return;"); + // Delegate the serialization logic to the method handling intersection types, + // as both require serializing all possible type variations. + WriteSerializationFunctionForCodeIntersectionType(composedType, composedParam, codeElement, writer); } private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminatorInfo, string paramName, CodeFunction codeElement, LanguageWriter writer) @@ -150,28 +174,28 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato writer.CloseBlock(); } - private void WriteComposedTypeSerializationForPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) + private void WriteSerializationFunctionForTypeComposedOfPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"if ({paramName} === undefined) return;"); writer.StartBlock($"switch (typeof {paramName}) {{"); - foreach (var type in composedType.Types) + foreach (var type in composedType.GetPrimitiveTypes()) { - WriteTypeSerialization(type, paramName, method, writer); + WriteCaseStatementForPrimitiveTypeSerialization(type, "key", paramName, method, writer); } writer.CloseBlock(); } - private void WriteTypeSerialization(CodeTypeBase type, string paramName, CodeFunction method, LanguageWriter writer) + private void WriteCaseStatementForPrimitiveTypeSerialization(CodeTypeBase type, string key, string value, CodeFunction method, LanguageWriter writer) { var nodeType = conventions.GetTypeString(type, method, false); var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod); if (string.IsNullOrEmpty(serializationName) || string.IsNullOrEmpty(nodeType)) return; writer.StartBlock($"case \"{nodeType}\":"); - writer.WriteLine($"writer.{serializationName}(key, {paramName});"); + writer.WriteLine($"writer.{serializationName}({key}, {value});"); writer.WriteLine($"break;"); writer.DecreaseIndent(); } @@ -206,7 +230,7 @@ private static void WriteSerializationRegistration(HashSet serialization writer.WriteLine($"{methodName}({module});"); } - private void WriteDiscriminatorFunction(CodeFunction codeElement, LanguageWriter writer) + private void WriteFactoryMethod(CodeFunction codeElement, LanguageWriter writer) { var returnType = conventions.GetTypeString(codeElement.OriginalLocalMethod.ReturnType, codeElement); @@ -226,7 +250,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: - WriteFactoryMethodBodyForCodeUnionType(codeElement, writer, parseNodeParameter); + WriteFactoryMethodBodyForCodeUnionType(codeElement, returnType, writer, parseNodeParameter); break; case CodeIntersectionType _ when parseNodeParameter != null: WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); @@ -237,11 +261,11 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, } } - private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, LanguageWriter writer, CodeParameter parseNodeParameter) + private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter parseNodeParameter) { WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); - // It's a composed type but there isn't a discriminator property - writer.WriteLine($"throw new Error(\"A discriminator property is required to distinguish a union type\");"); + // The default discriminator is useful when the discriminator information is not provided. + WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); } private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) @@ -256,9 +280,9 @@ private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string retur private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) { + var composedType = GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType); var deserializationFunction = GetFunctionName(codeElement, returnType, CodeMethodKind.Deserializer); - var parseNodeParameterForPrimitiveValues = GetParseNodeParameterForPrimitiveValues(codeElement, parseNodeParameter); - writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()}{parseNodeParameterForPrimitiveValues};"); + writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()};"); } private static bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement, CodeComposedTypeBase? composedType) @@ -268,32 +292,29 @@ private static bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParameter parseNodeParameter, LanguageWriter writer) { - writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}?.getChildNode(\"{codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorPropertyName}\");", - "if (mappingValueNode) {"); - writer.IncreaseIndent(); - writer.WriteLines("const mappingValue = mappingValueNode.getStringValue();", - "if (mappingValue) {"); - writer.IncreaseIndent(); + var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; + var discriminatorPropertyName = discriminatorInfo.DiscriminatorPropertyName; - writer.StartBlock("switch (mappingValue) {"); - foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) + if (!string.IsNullOrEmpty(discriminatorPropertyName)) { - writer.StartBlock($"case \"{mappedType.Key}\":"); - writer.WriteLine($"return {GetFunctionName(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase(), CodeMethodKind.Deserializer)};"); - writer.DecreaseIndent(); - } - writer.CloseBlock(); - writer.CloseBlock(); - writer.CloseBlock(); - } + writer.WriteLines($"const mappingValueNode = {parseNodeParameter.Name.ToFirstCharacterLowerCase()}?.getChildNode(\"{discriminatorPropertyName}\");", + "if (mappingValueNode) {"); + writer.IncreaseIndent(); + writer.WriteLines("const mappingValue = mappingValueNode.getStringValue();", + "if (mappingValue) {"); + writer.IncreaseIndent(); - private string GetParseNodeParameterForPrimitiveValues(CodeFunction codeElement, CodeParameter? parseNodeParameter) - { - if (GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType) is { } composedType && composedType.IsComposedOfPrimitives() && parseNodeParameter is not null) - { - return $"({parseNodeParameter.Name.ToFirstCharacterLowerCase()})"; + writer.StartBlock("switch (mappingValue) {"); + foreach (var mappedType in discriminatorInfo.DiscriminatorMappings) + { + writer.StartBlock($"case \"{mappedType.Key}\":"); + writer.WriteLine($"return {GetFunctionName(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase(), CodeMethodKind.Deserializer)};"); + writer.DecreaseIndent(); + } + writer.CloseBlock(); + writer.CloseBlock(); + writer.CloseBlock(); } - return string.Empty; } private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind) @@ -381,21 +402,50 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null) { var serializeName = GetSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}"); - writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + if (GetOriginalComposedType(propType.TypeDefinition) is { } ct && (ct.IsComposedOfPrimitives() || ct.IsComposedOfObjectsAndPrimitives())) + WriteSerializationStatementForComposedTypeProperty(ct, modelParamName, codeFunction, writer, codeProperty, serializeName); + else + writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); } else { if (!string.IsNullOrWhiteSpace(spreadOperator)) - { writer.WriteLine($"if({modelParamName}.{codePropertyName})"); - } - if (composedType is not null && composedType.IsComposedOfPrimitives()) - writer.WriteLine($"{serializationName}(writer, \"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); + if (composedType is not null && (composedType.IsComposedOfPrimitives() || composedType.IsComposedOfObjectsAndPrimitives())) + WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty); else writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); } } + private void WriteSerializationStatementForComposedTypeProperty(CodeComposedTypeBase composedType, string modelParamName, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string? serializeName) + { + var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); + var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; + var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); + var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); + + var serializationName = GetSerializationMethodName(codeProperty.Type, method.OriginalLocalMethod); + var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; + + writer.StartBlock($"switch (typeof {modelParamName}.{codePropertyName}) {{"); + + foreach (var type in composedType.GetPrimitiveTypes()) + { + WriteCaseStatementForPrimitiveTypeSerialization(type, $"\"{codeProperty.WireName}\"", $"{spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix}", method, writer); + } + + if (composedType.IsComposedOfObjectsAndPrimitives()) + { + // write the default statement serialization statement for the object + writer.StartBlock($"default:"); + writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + writer.WriteLine($"break;"); + writer.DecreaseIndent(); + } + + writer.CloseBlock(); + } public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { @@ -481,7 +531,7 @@ private void WriteDeserializerFunctionProperties(CodeParameter param, CodeInterf foreach (var otherProp in properties) { - WritePropertyBlock(otherProp, param, primaryErrorMapping, primaryErrorMappingKey, codeFunction, writer); + WritePropertyDeserializationBlock(otherProp, param, primaryErrorMapping, primaryErrorMappingKey, codeFunction, writer); } writer.CloseBlock(); @@ -510,22 +560,69 @@ private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param return (primaryErrorMapping, primaryErrorMappingKey); } - private void WritePropertyBlock(CodeProperty otherProp, CodeParameter param, string primaryErrorMapping, string primaryErrorMappingKey, CodeFunction codeFunction, LanguageWriter writer) + private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParameter param, string primaryErrorMapping, string primaryErrorMappingKey, CodeFunction codeFunction, LanguageWriter writer) { - var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; - if (otherProp.Kind is CodePropertyKind.BackingStore) - writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); - else if (GetOriginalComposedType(otherProp.Type) is { } composedType && composedType.IsComposedOfPrimitives()) + var suffix = GetSuffix(otherProp, primaryErrorMapping, primaryErrorMappingKey); + var paramName = param.Name.ToFirstCharacterLowerCase(); + var propName = otherProp.Name.ToFirstCharacterLowerCase(); + + if (IsBackingStoreProperty(otherProp)) + { + WriteBackingStoreProperty(writer, paramName, propName, suffix); + } + else if (GetOriginalComposedType(otherProp.Type) is { } composedType) { - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); + WriteComposedTypePropertyDeserialization(writer, otherProp, paramName, propName, composedType, codeFunction, suffix); } else { - var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod)}{defaultValueSuffix};{suffix} }},"); + WriteDefaultPropertyDeserialization(writer, otherProp, paramName, propName, codeFunction, suffix); + } + } + + private string GetSuffix(CodeProperty otherProp, string primaryErrorMapping, string primaryErrorMappingKey) + { + return otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; + } + + private bool IsBackingStoreProperty(CodeProperty otherProp) + { + return otherProp.Kind is CodePropertyKind.BackingStore; + } + + private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, string propName, string suffix) + { + writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); + } + + private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) + { + if (composedType.IsComposedOfPrimitives()) + { + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); + } + else if (composedType.IsComposedOfObjectsAndPrimitives()) + { + var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); + var primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false); + var defaultValueSuffix = GetDefaultValueSuffix(otherProp); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{objectSerializationMethodName}{defaultValueSuffix};{suffix} }},"); } } + private void WriteDefaultPropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeFunction codeFunction, string suffix) + { + var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); + var defaultValueSuffix = GetDefaultValueSuffix(otherProp); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = n.{objectSerializationMethodName}{defaultValueSuffix};{suffix} }},"); + } + + private string GetDefaultValueSuffix(CodeProperty otherProp) + { + var defaultValue = GetDefaultValueLiteralForProperty(otherProp); + return !string.IsNullOrEmpty(defaultValue) && !defaultValue.EqualsIgnoreCase("\"null\"") ? $" ?? {defaultValue}" : string.Empty; + } + private static string GetDefaultValueLiteralForProperty(CodeProperty codeProperty) { if (string.IsNullOrEmpty(codeProperty.DefaultValue)) return string.Empty; diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs index 1ae7bc711c..791f12964e 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs @@ -51,5 +51,86 @@ public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); Assert.False(composedType.IsComposedOfPrimitives()); } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_OnlyPrimitives_ReturnsFalse() + { + // Arrange + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_OnlyObjects_ReturnsFalse() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + + CodeClass td = new CodeClass + { + Name = "SomeClass" + }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_BothPrimitivesAndObjects_ReturnsTrue() + { + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + // Add primitive + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + CodeClass td = new CodeClass + { + Name = "SomeClass" + }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_EmptyTypes_ReturnsFalse() + { + // Arrange + var composedType = new CodeUnionType + { + Name = "test", + Parent = currentType + }; + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(); + + // Assert + Assert.False(result); + } } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index cfc8a7c224..80abf7ee4e 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1164,12 +1164,8 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() * @returns {ValidationError_errors_value} *\/ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | undefined) : Primitives | undefined { - if (parseNode) { - parseNode.getNumberValue() || parseNode.getStringValue(); - } - return undefined; + return parseNode?.getNumberValue() ?? parseNode?.getStringValue(); } - */ // Test Factory function @@ -1292,7 +1288,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() Assert.NotNull(modelCodeFile); // Test Serializer function - var serializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && GetOriginalComposedType(function.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null)) is not null); + var serializerFunction = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Serializer); Assert.True(serializerFunction is not null); writer.Write(serializerFunction); var serializerFunctionStr = tw.ToString(); From 5072ec6c7ed899d636608c998221a40748a680bd Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 29 Jul 2024 18:15:31 +0300 Subject: [PATCH 085/117] format code --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 956ff43df7..57d586774b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -443,7 +443,7 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType writer.WriteLine($"break;"); writer.DecreaseIndent(); } - + writer.CloseBlock(); } diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs index 791f12964e..d528c91f3c 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs @@ -79,7 +79,7 @@ public void IsComposedOfObjectsAndPrimitives_OnlyObjects_ReturnsFalse() Name = "test", Parent = currentType }; - + CodeClass td = new CodeClass { Name = "SomeClass" From 4fdd941c979a3f27bdd40b6f5978a0689fda50ec Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 29 Jul 2024 18:31:24 +0300 Subject: [PATCH 086/117] remove unused code in CodeMethod.cs --- src/Kiota.Builder/CodeDOM/CodeMethod.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 897178cb1e..006960ec8c 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -120,27 +120,6 @@ public static CodeMethod FromIndexer(CodeIndexer originalIndexer, Func Date: Mon, 29 Jul 2024 18:59:42 +0300 Subject: [PATCH 087/117] delete unused method --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 57d586774b..72faee80d8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -53,12 +53,6 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w } } - private string GetReturnTypeForPrimitiveComposedTypes(CodeComposedTypeBase? composedType) - { - if (composedType == null) return string.Empty; - return " | " + string.Join(" | ", composedType.GetPrimitiveTypes().Select(x => conventions.GetTypeString(x, composedType, false))); - } - private string GetSerializationMethodsForPrimitiveUnionTypes(CodeComposedTypeBase composedType, string parseNodeParameterName, CodeFunction codeElement, bool nodeParameterCanBeNull = true) { var optionalChainingSymbol = nodeParameterCanBeNull ? "?" : string.Empty; From fc32d3133316f5ca8ed24d9db5646ed413750ff4 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 30 Jul 2024 13:51:26 +0300 Subject: [PATCH 088/117] import all associated deserializers for composed types --- it/config.json | 4 -- .../Refiners/TypeScriptRefiner.cs | 38 ++++++++++++++++++- .../Writers/TypeScript/CodeFunctionWriter.cs | 9 ++--- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/it/config.json b/it/config.json index 5ba33e8ca5..d9984f61c4 100644 --- a/it/config.json +++ b/it/config.json @@ -234,10 +234,6 @@ }, "apisguru::stripe.com": { "Suppressions": [ - { - "Language": "typescript", - "Rationale": "This document includes an intersection between objects and primitive values, which is not supported in the TypeScript language and results in compilation errors." - }, { "Language": "go", "Rationale": "https://github.com/microsoft/kiota/issues/2834" diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index f31ae698c8..8f9136e18f 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -1359,8 +1359,45 @@ private static void AddPropertyFactoryUsingToDeserializer(CodeFunction codeFunct } } + /// + /// Adds all the required import statements (CodeUsings) to the deserialization function which have a dependency on ComposedTypes. + /// Composed types can be comprised of other interfaces/classes. + /// + /// The code element to process. + private static void AddDeserializerUsingToDiscriminatorFactoryForComposedTypeParameters(CodeElement codeElement) + { + if (codeElement is not CodeFunction function) return; + + var composedTypeParam = function.OriginalLocalMethod.Parameters + .FirstOrDefault(x => GetOriginalComposedType(x) is not null); + + if (composedTypeParam is null) return; + + var composedType = GetOriginalComposedType(composedTypeParam); + if (composedType is null) return; + + foreach (var type in composedType.AllTypes) + { + if (type.TypeDefinition is not CodeInterface codeInterface) continue; + + var modelDeserializerFunction = GetSerializationFunctionsForNamespace(codeInterface.OriginalClass).Item2; + if (modelDeserializerFunction.Parent is null) continue; + + function.AddUsing(new CodeUsing + { + Name = modelDeserializerFunction.Parent.Name, + Declaration = new CodeType + { + Name = modelDeserializerFunction.Name, + TypeDefinition = modelDeserializerFunction + }, + }); + } + } + private static void AddDeserializerUsingToDiscriminatorFactory(CodeElement codeElement) { + AddDeserializerUsingToDiscriminatorFactoryForComposedTypeParameters(codeElement); if (codeElement is CodeFunction parsableFactoryFunction && parsableFactoryFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory) && parsableFactoryFunction.OriginalLocalMethod?.ReturnType is CodeType codeType && codeType.TypeDefinition is CodeClass modelReturnClass) { @@ -1398,7 +1435,6 @@ private static void AddDeserializerUsingToDiscriminatorFactory(CodeElement codeE } } } - } CrawlTree(codeElement, AddDeserializerUsingToDiscriminatorFactory); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 72faee80d8..529eec6511 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -247,7 +247,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, WriteFactoryMethodBodyForCodeUnionType(codeElement, returnType, writer, parseNodeParameter); break; case CodeIntersectionType _ when parseNodeParameter != null: - WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); + WriteDefaultDiscriminator(codeElement, returnType, writer); break; default: WriteNormalFactoryMethodBody(codeElement, returnType, writer); @@ -259,7 +259,7 @@ private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, st { WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); // The default discriminator is useful when the discriminator information is not provided. - WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); + WriteDefaultDiscriminator(codeElement, returnType, writer); } private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) @@ -269,12 +269,11 @@ private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string retur { WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); } - WriteDefaultDiscriminator(codeElement, returnType, writer, parseNodeParameter); + WriteDefaultDiscriminator(codeElement, returnType, writer); } - private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter? parseNodeParameter) + private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer) { - var composedType = GetOriginalComposedType(codeElement.OriginalLocalMethod.ReturnType); var deserializationFunction = GetFunctionName(codeElement, returnType, CodeMethodKind.Deserializer); writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()};"); } From 83f95bf2bdce9ef726e1c14f8891d3bd2728b199 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 1 Aug 2024 13:09:33 +0300 Subject: [PATCH 089/117] remove collection symbol for composed type param in serailizer and deserializer functions --- .../Refiners/TypeScriptRefiner.cs | 20 +++++++++++++++++-- .../TypeScript/TypeScriptConventionService.cs | 16 +++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 8f9136e18f..80a61ba416 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -1265,6 +1265,20 @@ protected static void AddEnumObjectUsings(CodeElement currentElement) CrawlTree(currentElement, AddEnumObjectUsings); } + private static void AddCodeUsingForComposedTypeProperty(CodeType propertyType, CodeInterface modelInterface, Func interfaceNamingCallback) + { + // If the property is a composed type then add code using for each of the classes contained in the composed type + if (GetOriginalComposedType(propertyType) is not { } composedTypeProperty) return; + foreach (var composedType in composedTypeProperty.AllTypes) + { + if (composedType.TypeDefinition is CodeClass composedTypePropertyClass) + { + var composedTypePropertyInterfaceTypeAndUsing = GetUpdatedModelInterfaceAndCodeUsing(composedTypePropertyClass, composedType, interfaceNamingCallback); + SetUsingInModelInterface(modelInterface, composedTypePropertyInterfaceTypeAndUsing); + } + } + } + private static void ProcessModelClassProperties(CodeClass modelClass, CodeInterface modelInterface, IEnumerable properties, Func interfaceNamingCallback) { /* @@ -1290,7 +1304,9 @@ private static void ProcessModelClassProperties(CodeClass modelClass, CodeInterf } else if (mProp.Type is CodeType propertyType && propertyType.TypeDefinition is CodeClass propertyClass) { - var interfaceTypeAndUsing = ReturnUpdatedModelInterfaceTypeAndUsing(propertyClass, propertyType, interfaceNamingCallback); + AddCodeUsingForComposedTypeProperty(propertyType, modelInterface, interfaceNamingCallback); + + var interfaceTypeAndUsing = GetUpdatedModelInterfaceAndCodeUsing(propertyClass, propertyType, interfaceNamingCallback); SetUsingInModelInterface(modelInterface, interfaceTypeAndUsing); // In case of a serializer function, the object serializer function will hold reference to serializer function of the property type. @@ -1309,7 +1325,7 @@ private static void ProcessModelClassProperties(CodeClass modelClass, CodeInterf private const string FactorySuffix = "FromDiscriminatorValue"; private static string GetFactoryFunctionNameFromTypeName(string? typeName) => string.IsNullOrEmpty(typeName) ? string.Empty : $"{FactoryPrefix}{typeName.ToFirstCharacterUpperCase()}{FactorySuffix}"; - private static (CodeInterface?, CodeUsing?) ReturnUpdatedModelInterfaceTypeAndUsing(CodeClass sourceClass, CodeType originalType, Func interfaceNamingCallback) + private static (CodeInterface?, CodeUsing?) GetUpdatedModelInterfaceAndCodeUsing(CodeClass sourceClass, CodeType originalType, Func interfaceNamingCallback) { var propertyInterfaceType = CreateModelInterface(sourceClass, interfaceNamingCallback); if (propertyInterfaceType.Parent is null) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index f42d4bfcf9..dfebdc8f27 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -80,10 +80,18 @@ public override string GetAccessModifier(AccessModifier access) }; } + private static bool ShouldIncludeCollectionInformationForParameter(CodeParameter parameter) + { + return !(GetOriginalComposedType(parameter) is not null + && parameter.Parent is CodeMethod codeMethod + && (codeMethod.IsOfKind(CodeMethodKind.Serializer) || codeMethod.IsOfKind(CodeMethodKind.Deserializer))); + } + public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) { ArgumentNullException.ThrowIfNull(parameter); - var paramType = GetTypescriptTypeString(parameter.Type, targetElement, inlineComposedTypeString: true); + var includeCollectionInformation = ShouldIncludeCollectionInformationForParameter(parameter); + var paramType = GetTypescriptTypeString(parameter.Type, targetElement, includeCollectionInformation: includeCollectionInformation, inlineComposedTypeString: true); var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { @@ -115,7 +123,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ if (inlineComposedTypeString && composedType?.Types.Any() == true) { - return GetComposedTypeTypeString(composedType, targetElement, collectionSuffix); + return GetComposedTypeTypeString(composedType, targetElement, collectionSuffix, includeCollectionInformation: includeCollectionInformation); } CodeTypeBase codeType = composedType is not null ? new CodeType() { TypeDefinition = composedType } : code; @@ -142,10 +150,10 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ * @param targetElement The target element * @returns The composed type string representation */ - private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix) + private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix, bool includeCollectionInformation = true) { if (!composedType.Types.Any()) throw new InvalidOperationException($"Composed type should be comprised of at least one type"); - var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement))); + var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation: includeCollectionInformation))); return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; } From 40c226f3e4b063bd75ccb0f0f4afc0eea28c180e Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 5 Aug 2024 12:47:46 +0300 Subject: [PATCH 090/117] handle endge cases where composed type is a mix of objects and/or array of objects and/or primitive values --- .../Refiners/TypeScriptRefiner.cs | 38 +++++++++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 66 ++++++++++++++++--- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 80a61ba416..ffdc54e586 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -339,6 +339,41 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase // Add the key parameter if the composed type is a union of primitive values if (composedType.IsComposedOfPrimitives()) function.OriginalLocalMethod.AddParameter(CreateKeyParameter()); + + // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces + AddSerializationUsingsForCodeComposed(composedType, function, CodeMethodKind.Serializer); + } + + private static void AddSerializationUsingsForCodeComposed(CodeComposedTypeBase composedType, CodeFunction function, CodeMethodKind kind) + { + // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces + foreach (var type in composedType.GetNonPrimitiveTypes()) + { + if (type.TypeDefinition is CodeInterface codeInterface && codeInterface.OriginalClass is CodeClass codeClass) + { + var (serializer, deserializer) = GetSerializationFunctionsForNamespace(codeClass); + if (kind == CodeMethodKind.Serializer) + AddSerializationUsingsToFunction(function, serializer); + if (kind == CodeMethodKind.Deserializer) + AddSerializationUsingsToFunction(function, deserializer); + } + } + } + + private static void AddSerializationUsingsToFunction(CodeFunction function, CodeFunction serializationFunction) + { + if (serializationFunction.Parent is not null) + { + function.AddUsing(new CodeUsing + { + Name = serializationFunction.Parent.Name, + Declaration = new CodeType + { + Name = serializationFunction.Name, + TypeDefinition = serializationFunction + } + }); + } } private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeInterface, CodeNamespace codeNamespace, CodeComposedTypeBase composedType, List children) @@ -352,6 +387,9 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI codeInterface.RemoveChildElement(deserializerMethod); codeNamespace.RemoveChildElement(deserializerMethod); } + + // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces + AddSerializationUsingsForCodeComposed(composedType, deserializerMethod, CodeMethodKind.Deserializer); } private static CodeParameter CreateKeyParameter() diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 529eec6511..74b58c67b5 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -81,7 +81,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName}),"); + writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, codeElement, includeCollectionInformation: false, inlineComposedTypeString: true)}),"); } writer.CloseBlock(); } @@ -111,10 +111,28 @@ private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeB foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {mappedTypeName});"); + writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, method, includeCollectionInformation: false, inlineComposedTypeString: true)});"); } } + public static bool IsComposedOfCollectionOfObjects(CodeComposedTypeBase composedType) + { + ArgumentNullException.ThrowIfNull(composedType); + // Get the objects + var composedTypeObjects = composedType.Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, composedType))); + // Get the collections count + return composedTypeObjects.Any(x => x.IsCollection); + } + + public static bool IsComposedOfNonCollectionOfObjects(CodeComposedTypeBase composedType) + { + ArgumentNullException.ThrowIfNull(composedType); + // Get the objects + var composedTypeObjects = composedType.Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, composedType))); + // Get the non collections count + return composedTypeObjects.Any(x => !x.IsCollection); + } + private void WriteSerializationFunctionForCodeUnionTypes(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; @@ -418,7 +436,6 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); - var serializationName = GetSerializationMethodName(codeProperty.Type, method.OriginalLocalMethod); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; writer.StartBlock($"switch (typeof {modelParamName}.{codePropertyName}) {{"); @@ -432,7 +449,14 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType { // write the default statement serialization statement for the object writer.StartBlock($"default:"); - writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + if (IsComposedOfCollectionOfObjects(composedType)) + { + writer.WriteLine($"writer.writeCollectionOfObjectValues<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + } + if (IsComposedOfNonCollectionOfObjects(composedType)) + { + writer.WriteLine($"writer.writeObjectValue<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + } writer.WriteLine($"break;"); writer.DecreaseIndent(); } @@ -588,19 +612,43 @@ private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); } - private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) + private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) { + var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); if (composedType.IsComposedOfPrimitives()) { - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = {GetFactoryMethodName(otherProp.Type, codeFunction)}(n); }},"); + writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {GetFactoryMethodName(codeProperty.Type, codeFunction)}(n); }},"); } else if (composedType.IsComposedOfObjectsAndPrimitives()) { - var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); var primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false); - var defaultValueSuffix = GetDefaultValueSuffix(otherProp); - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{objectSerializationMethodName}{defaultValueSuffix};{suffix} }},"); + writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{GetObjectTypeSerializationMethodName(codeProperty, codeFunction)}{defaultValueSuffix};{suffix} }},"); } + else // its composed of objects + { + // check if its all comprised items are a collection + if (IsComposedOfNonCollectionOfObjects(composedType)) + { + var serializationMethodName = composedType.IsCollection ? GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction) : GetObjectTypeSerializationMethodName(codeProperty, codeFunction); + writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = n.{serializationMethodName}{defaultValueSuffix};{suffix} }},"); + } + else if (IsComposedOfCollectionOfObjects(composedType)) + { + writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = n.{GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction)}{defaultValueSuffix};{suffix} }},"); + } + } + } + + private string GetObjectTypeSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) + { + var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); + return $"getObjectValue<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; + } + + private string GetCollectionOfObjectsSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) + { + var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); + return $"getCollectionOfObjectValues<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; } private void WriteDefaultPropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeFunction codeFunction, string suffix) From f31bad49f2b247b3200427b1f9ff346b3f9fc713 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 5 Aug 2024 13:04:46 +0300 Subject: [PATCH 091/117] fix casing --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 74b58c67b5..03bf22300c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -642,13 +642,13 @@ private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, Cod private string GetObjectTypeSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) { var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); - return $"getObjectValue<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; + return $"getObjectValue<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; } private string GetCollectionOfObjectsSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) { var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); - return $"getCollectionOfObjectValues<{propertyType.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; + return $"getCollectionOfObjectValues<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; } private void WriteDefaultPropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeFunction codeFunction, string suffix) From 9a34c233ead7aaa93de8edcd6fcf874607eb4ea6 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 6 Aug 2024 15:09:41 +0300 Subject: [PATCH 092/117] add serialization functions to obsolete class definitions to prevent typescript refiner from throwing exceptions --- src/Kiota.Builder/KiotaBuilder.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index f3535b3cf0..84102508a4 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1221,6 +1221,12 @@ private void AddErrorMappingToExecutorMethod(OpenApiUrlTreeNode currentNode, Ope var obsoleteFactoryMethod = (CodeMethod)originalFactoryMethod.Clone(); obsoleteFactoryMethod.ReturnType = new CodeType { Name = obsoleteTypeName, TypeDefinition = obsoleteClassDefinition }; obsoleteClassDefinition.AddMethod(obsoleteFactoryMethod); + var originalSerializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Serializer); + var obsoleteSerializerMethod = (CodeMethod)originalSerializerMethod.Clone(); + obsoleteClassDefinition.AddMethod(obsoleteSerializerMethod); + var originalDeserializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Deserializer); + var obsoleteDeserializerMethod = (CodeMethod)originalDeserializerMethod.Clone(); + obsoleteClassDefinition.AddMethod(obsoleteDeserializerMethod); obsoleteClassDefinition.StartBlock.Inherits = (CodeType)codeType.Clone(); var obsoleteClass = codeClass.Parent switch { From c725e83523b8720dda413170b0e1927bd7c8a0eb Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 6 Aug 2024 15:12:16 +0300 Subject: [PATCH 093/117] use correct serialization method for composed type collection --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 03bf22300c..078cd25614 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -621,8 +621,9 @@ private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, Cod } else if (composedType.IsComposedOfObjectsAndPrimitives()) { + var serializationMethodName = composedType.IsCollection || IsComposedOfCollectionOfObjects(composedType) ? GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction) : GetObjectTypeSerializationMethodName(codeProperty, codeFunction); var primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false); - writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{GetObjectTypeSerializationMethodName(codeProperty, codeFunction)}{defaultValueSuffix};{suffix} }},"); + writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{serializationMethodName}{defaultValueSuffix};{suffix} }},"); } else // its composed of objects { From 1d7937bd46bb228403653779a0afc196c0b7802f Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 6 Aug 2024 16:05:59 +0300 Subject: [PATCH 094/117] improve code coverage --- .../UnionOfPrimitiveAndObjects.cs | 83 ++++++++++++ .../TypeScript/CodeFunctionWriterTests.cs | 121 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs new file mode 100644 index 0000000000..cbdb6d7211 --- /dev/null +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs @@ -0,0 +1,83 @@ +namespace Kiota.Builder.Tests.OpenApiSampleFiles; + +public static class UnionOfPrimitiveAndObjects +{ + /** + * An OpenAPI 3.0.1 sample document with union between objects and primitive types + */ + public static readonly string openApiSpec = @" +openapi: 3.0.3 +info: + title: Pet API + description: An API to return pet information. + version: 1.0.0 +servers: + - url: http://localhost:8080 + description: Local server + +paths: + /pet: + get: + summary: Get pet information + operationId: getPet + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + request_id: + type: string + example: ""123e4567-e89b-12d3-a456-426614174000"" + data: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + - type: string + description: Error message + example: ""An error occurred while processing the request."" + - type: integer + description: Error code + example: 409 + '400': + description: Bad Request + '500': + description: Internal Server Error + +components: + schemas: + Pet: + type: object + required: + - name + - age + properties: + name: + type: string + example: ""Fluffy"" + age: + type: integer + example: 4 + + Cat: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + favoriteToy: + type: string + example: ""Mouse"" + + Dog: + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + breed: + type: string + example: ""Labrador"" +"; + +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 80abf7ee4e..dc3f17d65c 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1413,5 +1413,126 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() Assert.Contains("serializeFoo(writer, fooBar as Foo);", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } + + [Fact] + public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Factory() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Pet", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Factory function + var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory); + Assert.True(function is not null); + writer.Write(function); + var factoryFunctionStr = tw.ToString(); + Assert.Contains("@returns {Cat | Dog | number | string}", factoryFunctionStr); + Assert.Contains("createPetGetResponse_dataFromDiscriminatorValue", factoryFunctionStr); + Assert.Contains("deserializeIntoPetGetResponse_data", factoryFunctionStr); + AssertExtensions.CurlyBracesAreClosed(factoryFunctionStr, 1); + } + + [Fact] + public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Pet", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.Name.EqualsIgnoreCase("serializePetGetResponse")); + Assert.True(function is not null); + writer.Write(function); + var serializerFunctionStr = tw.ToString(); + + Assert.Contains("switch (typeof petGetResponse.data)", serializerFunctionStr); + Assert.Contains("case \"number\":", serializerFunctionStr); + Assert.Contains("writer.writeNumberValue(\"data\", petGetResponse.data);", serializerFunctionStr); + Assert.Contains("case \"string\":", serializerFunctionStr); + Assert.Contains("writer.writeStringValue(\"data\", petGetResponse.data);", serializerFunctionStr); + Assert.Contains("default:", serializerFunctionStr); + Assert.Contains("writer.writeObjectValue(\"data\", petGetResponse.data, serializePetGetResponse_data);", serializerFunctionStr); + + AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + } + + [Fact] + public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Deserializer() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Pet", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Empty(constructor.SerializerModules); + Assert.Empty(constructor.DeserializerModules); + await ILanguageRefiner.Refine(generationConfiguration, rootNS); + Assert.NotNull(rootNS); + var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); + Assert.NotNull(modelsNS); + var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); + Assert.NotNull(modelCodeFile); + + // Test Serializer function + var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.Name.EqualsIgnoreCase("deserializeIntoPetGetResponse")); + Assert.True(function is not null); + writer.Write(function); + var deserializerFunctionStr = tw.ToString(); + Assert.Contains("\"data\": n => { petGetResponse.data = n.getNumberValue() ?? n.getStringValue() ?? n.getObjectValue(createPetGetResponse_dataFromDiscriminatorValue); }", deserializerFunctionStr); + AssertExtensions.CurlyBracesAreClosed(deserializerFunctionStr, 1); + } } From d6953fb15481b4da1fc79fcfccce140189c7a9f5 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:40:13 +0300 Subject: [PATCH 095/117] Refactor composed types --- .../CodeDOM/CodeComposedTypeBase.cs | 16 ++- .../Refiners/TypeScriptRefiner.cs | 8 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 28 ++-- .../TypeScript/TypeScriptConventionService.cs | 4 +- .../CodeDOM/CodeComposedTypeBaseTests.cs | 136 ------------------ .../TypeScriptConventionServiceTests.cs | 90 +++++++++++- 6 files changed, 119 insertions(+), 163 deletions(-) delete mode 100644 tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index 4b3c4ea040..f12a127ae6 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -67,30 +67,32 @@ public CodeNamespace? TargetNamespace { get; set; } + public DeprecationInformation? Deprecation { get; set; } - public bool IsComposedOfPrimitives() => Types.All(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); - public bool IsComposedOfObjectsAndPrimitives() + + public bool IsComposedOfPrimitives(Func checkIfPrimitive) => Types.All(x => checkIfPrimitive(x, this)); + public bool IsComposedOfObjectsAndPrimitives(Func checkIfPrimitive) { // Count the number of primitives in Types - int primitiveCount = Types.Count(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); + int primitiveCount = Types.Count(x => checkIfPrimitive(x, this)); // If the number of primitives is less than the total count, it means the rest are objects return primitiveCount > 0 && primitiveCount < Types.Count(); } - public IEnumerable GetPrimitiveTypes() + public IEnumerable GetPrimitiveTypes(Func checkIfPrimitive) { // Return only the primitive types from the Types collection - return Types.Where(x => IsPrimitiveType(GetTypescriptTypeString(x, this))); + return Types.Where(x => checkIfPrimitive(x, this)); } - public IEnumerable GetNonPrimitiveTypes() + public IEnumerable GetNonPrimitiveTypes(Func checkIfPrimitive) { // Return only the non primitive types from the Types collection - return Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, this))); + return Types.Where(x => !checkIfPrimitive(x, this)); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index ffdc54e586..3c251a4a4e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -324,7 +324,7 @@ private static void ReplaceFactoryMethodForComposedType(CodeComposedTypeBase com { if (composedType is null || FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; - if (composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { function.OriginalLocalMethod.ReturnType = composedType; // Remove the deserializer import statement if its not being used @@ -337,7 +337,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; // Add the key parameter if the composed type is a union of primitive values - if (composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) function.OriginalLocalMethod.AddParameter(CreateKeyParameter()); // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces @@ -347,7 +347,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase private static void AddSerializationUsingsForCodeComposed(CodeComposedTypeBase composedType, CodeFunction function, CodeMethodKind kind) { // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces - foreach (var type in composedType.GetNonPrimitiveTypes()) + foreach (var type in composedType.GetNonPrimitiveTypes(IsComposedPrimitive)) { if (type.TypeDefinition is CodeInterface codeInterface && codeInterface.OriginalClass is CodeClass codeClass) { @@ -381,7 +381,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; // Deserializer function is not required for primitive values - if (composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { children.Remove(deserializerMethod); codeInterface.RemoveChildElement(deserializerMethod); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index b3e2f8224d..560807b57b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -24,7 +24,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; var composedType = GetOriginalComposedType(codeMethod.ReturnType); - var isComposedOfPrimitives = composedType is not null && composedType.IsComposedOfPrimitives(); + var isComposedOfPrimitives = composedType is not null && composedType.IsComposedOfPrimitives(IsComposedPrimitive); var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : @@ -57,7 +57,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w private string GetSerializationMethodsForPrimitiveUnionTypes(CodeComposedTypeBase composedType, string parseNodeParameterName, CodeFunction codeElement, bool nodeParameterCanBeNull = true) { var optionalChainingSymbol = nodeParameterCanBeNull ? "?" : string.Empty; - return string.Join(" ?? ", composedType.GetPrimitiveTypes().Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + return string.Join(" ?? ", composedType.GetPrimitiveTypes(IsComposedPrimitive).Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); } private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) @@ -79,7 +79,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri writer.StartBlock("return {"); // Serialization/Deserialization functions can be called for object types only - foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) + foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, codeElement, includeCollectionInformation: false, inlineComposedTypeString: true)}),"); @@ -91,7 +91,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite { if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; - if (composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { WriteSerializationFunctionForTypeComposedOfPrimitives(composedType, composedParam, codeElement, writer); return; @@ -109,7 +109,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { // Serialization/Deserialization functions can be called for object types only - foreach (var mappedType in composedType.GetNonPrimitiveTypes().ToArray()) + foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) { var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, method, includeCollectionInformation: false, inlineComposedTypeString: true)});"); @@ -193,7 +193,7 @@ private void WriteSerializationFunctionForTypeComposedOfPrimitives(CodeComposedT writer.WriteLine($"if ({paramName} === undefined) return;"); writer.StartBlock($"switch (typeof {paramName}) {{"); - foreach (var type in composedType.GetPrimitiveTypes()) + foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) { WriteCaseStatementForPrimitiveTypeSerialization(type, "key", paramName, method, writer); } @@ -259,7 +259,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when type.IsComposedOfPrimitives(): + case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsComposedPrimitive): WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: @@ -416,7 +416,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null) { var serializeName = GetSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}"); - if (GetOriginalComposedType(propType.TypeDefinition) is { } ct && (ct.IsComposedOfPrimitives() || ct.IsComposedOfObjectsAndPrimitives())) + if (GetOriginalComposedType(propType.TypeDefinition) is { } ct && (ct.IsComposedOfPrimitives(IsComposedPrimitive) || ct.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive))) WriteSerializationStatementForComposedTypeProperty(ct, modelParamName, codeFunction, writer, codeProperty, serializeName); else writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); @@ -425,7 +425,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { if (!string.IsNullOrWhiteSpace(spreadOperator)) writer.WriteLine($"if({modelParamName}.{codePropertyName})"); - if (composedType is not null && (composedType.IsComposedOfPrimitives() || composedType.IsComposedOfObjectsAndPrimitives())) + if (composedType is not null && (composedType.IsComposedOfPrimitives(IsComposedPrimitive) || composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive))) WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty); else writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); @@ -443,12 +443,12 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType writer.StartBlock($"switch (typeof {modelParamName}.{codePropertyName}) {{"); - foreach (var type in composedType.GetPrimitiveTypes()) + foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) { WriteCaseStatementForPrimitiveTypeSerialization(type, $"\"{codeProperty.WireName}\"", $"{spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix}", method, writer); } - if (composedType.IsComposedOfObjectsAndPrimitives()) + if (composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) { // write the default statement serialization statement for the object writer.StartBlock($"default:"); @@ -473,7 +473,7 @@ public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod m ArgumentNullException.ThrowIfNull(method); var composedType = GetOriginalComposedType(propertyType); - if (composedType is not null && composedType.IsComposedOfPrimitives()) + if (composedType is not null && composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; } @@ -618,11 +618,11 @@ private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) { var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); - if (composedType.IsComposedOfPrimitives()) + if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {GetFactoryMethodName(codeProperty.Type, codeFunction)}(n); }},"); } - else if (composedType.IsComposedOfObjectsAndPrimitives()) + else if (composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) { var serializationMethodName = composedType.IsCollection || IsComposedOfCollectionOfObjects(composedType) ? GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction) : GetObjectTypeSerializationMethodName(codeProperty, codeFunction); var primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false); diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 46b8607b27..950e4450de 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -92,7 +92,7 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen ArgumentNullException.ThrowIfNull(parameter); var includeCollectionInformation = ShouldIncludeCollectionInformationForParameter(parameter); var paramType = GetTypescriptTypeString(parameter.Type, targetElement, includeCollectionInformation: includeCollectionInformation, inlineComposedTypeString: true); - var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(); + var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(IsComposedPrimitive); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { (false, CodeParameterKind.DeserializationTarget, false) when parameter.Parent is CodeMethod codeMethod && codeMethod.Kind is CodeMethodKind.Serializer @@ -219,6 +219,8 @@ TYPE_LOWERCASE_BOOLEAN or }; } + public static bool IsComposedPrimitive(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(GetTypescriptTypeString(codeType, codeComposedTypeBase)); + internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription?.Replace("\\", "/", StringComparison.OrdinalIgnoreCase) ?? string.Empty; public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") { diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs deleted file mode 100644 index d528c91f3c..0000000000 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeComposedTypeBaseTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Linq; -using Kiota.Builder.CodeDOM; -using Xunit; - -namespace Kiota.Builder.Tests.CodeDOM -{ - public class CodeComposedTypeBaseTests - { - private readonly CodeType currentType; - private const string TypeName = "SomeType"; - public CodeComposedTypeBaseTests() - { - currentType = new() - { - Name = TypeName - }; - var root = CodeNamespace.InitRootNamespace(); - var parentClass = root.AddClass(new CodeClass - { - Name = "ParentClass" - }).First(); - currentType.Parent = parentClass; - } - - [Fact] - public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); - Assert.True(composedType.IsComposedOfPrimitives()); - } - - [Fact] - public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - CodeClass td = new CodeClass - { - Name = "SomeClass" - }; - composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); - Assert.False(composedType.IsComposedOfPrimitives()); - } - - [Fact] - public void IsComposedOfObjectsAndPrimitives_OnlyPrimitives_ReturnsFalse() - { - // Arrange - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - - // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsComposedOfObjectsAndPrimitives_OnlyObjects_ReturnsFalse() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - - CodeClass td = new CodeClass - { - Name = "SomeClass" - }; - composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); - - // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsComposedOfObjectsAndPrimitives_BothPrimitivesAndObjects_ReturnsTrue() - { - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - // Add primitive - composedType.AddType(new CodeType { Name = "string", IsExternal = true }); - CodeClass td = new CodeClass - { - Name = "SomeClass" - }; - composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); - - // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsComposedOfObjectsAndPrimitives_EmptyTypes_ReturnsFalse() - { - // Arrange - var composedType = new CodeUnionType - { - Name = "test", - Parent = currentType - }; - - // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(); - - // Assert - Assert.False(result); - } - } -} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index df1f9a6af1..b20b5127d1 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Writers.TypeScript; using Xunit; @@ -7,6 +7,7 @@ namespace Kiota.Builder.Tests.Writers.TypeScript; public class TypeScriptConventionServiceTests { + [Fact] public void TranslateType_ThrowsArgumentNullException_WhenComposedTypeIsNull() { @@ -21,4 +22,91 @@ public void TranslateType_ReturnsCorrectTranslation_WhenComposedTypeIsNotNull() var result = TypeScriptConventionService.TranslateTypescriptType(composedType); Assert.Equal("Test", result); } + + public CodeType CurrentType() + { + CodeType currentType = new CodeType { Name = "SomeType" }; + var root = CodeNamespace.InitRootNamespace(); + var parentClass = root.AddClass(new CodeClass { Name = "ParentClass" }).First(); + currentType.Parent = parentClass; + return currentType; + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() + { + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); + Assert.True(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsComposedPrimitive)); + } + + [Fact] + public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() + { + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + var td = new CodeClass { Name = "SomeClass" }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + Assert.False(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsComposedPrimitive)); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_OnlyPrimitives_ReturnsFalse() + { + // Arrange + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_OnlyObjects_ReturnsFalse() + { + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + + var td = new CodeClass { Name = "SomeClass" }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_BothPrimitivesAndObjects_ReturnsTrue() + { + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + // Add primitive + composedType.AddType(new CodeType { Name = "string", IsExternal = true }); + var td = new CodeClass { Name = "SomeClass" }; + composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsComposedOfObjectsAndPrimitives_EmptyTypes_ReturnsFalse() + { + // Arrange + var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; + + // Act + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + + // Assert + Assert.False(result); + } } From ed4888d8ac7a71763dedc8aaba23f40df7f53270 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:54:14 +0300 Subject: [PATCH 096/117] fix: null check on composed types --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- .../Writers/TypeScript/TypeScriptConventionServiceTests.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 560807b57b..4cf2e8aa1f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -146,7 +146,7 @@ private void WriteSerializationFunctionForCodeUnionTypes(CodeComposedTypeBase co } var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} === undefined) return;"); + writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;"); WriteDiscriminatorSwitchBlock(discriminatorInfo, paramName, codeElement, writer); } @@ -190,7 +190,7 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato private void WriteSerializationFunctionForTypeComposedOfPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} === undefined) return;"); + writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;"); writer.StartBlock($"switch (typeof {paramName}) {{"); foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) @@ -220,7 +220,7 @@ private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod meth if (method.Parameters.OfKind(CodeParameterKind.RequestAdapter)?.Name.ToFirstCharacterLowerCase() is not string requestAdapterArgumentName) return; if (!string.IsNullOrEmpty(method.BaseUrl)) { - writer.StartBlock($"if ({requestAdapterArgumentName}.baseUrl === undefined || {requestAdapterArgumentName}.baseUrl === \"\") {{"); + writer.StartBlock($"if ({requestAdapterArgumentName}.baseUrl === undefined || {requestAdapterArgumentName}.baseUrl === null || {requestAdapterArgumentName}.baseUrl === \"\") {{"); writer.WriteLine($"{requestAdapterArgumentName}.baseUrl = \"{method.BaseUrl}\";"); writer.CloseBlock(); } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index b20b5127d1..a00a8699e5 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -22,7 +22,7 @@ public void TranslateType_ReturnsCorrectTranslation_WhenComposedTypeIsNotNull() var result = TypeScriptConventionService.TranslateTypescriptType(composedType); Assert.Equal("Test", result); } - + public CodeType CurrentType() { CodeType currentType = new CodeType { Name = "SomeType" }; From a69e620daf7dea35b4de694b6ccff0f4d46900ba Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:20:13 +0300 Subject: [PATCH 097/117] fix: type casting --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 4cf2e8aa1f..8656ce9634 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -179,7 +179,7 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato { writer.StartBlock($"case \"{mappedType.Key}\":"); var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)}(writer, {paramName});"); + writer.WriteLine($"{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)}(writer, {paramName} as {mappedType.Value.AllTypes.First().Name.ToFirstCharacterUpperCase()});"); writer.WriteLine("break;"); writer.DecreaseIndent(); } From 69a6c1a7b145393318a683ddcf90d5fae23605fc Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:09:16 +0300 Subject: [PATCH 098/117] Fix serialization --- .../Writers/TypeScript/CodeFunctionWriter.cs | 73 ++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 8656ce9634..b82d854ca4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -75,7 +75,7 @@ private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedTy private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWriter writer, CodeParameter composedParam) { - if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; + if (GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); // Serialization/Deserialization functions can be called for object types only @@ -89,7 +89,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWriter writer, CodeParameter composedParam) { - if (composedParam is null || GetOriginalComposedType(composedParam) is not { } composedType) return; + if (GetOriginalComposedType(composedParam) is not { } composedType) return; if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { @@ -615,8 +615,9 @@ private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); } - private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) + private void WriteComposedTypePropertyDeserialization1(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) { + // for composed types with a discriminator, var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { @@ -643,18 +644,84 @@ private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, Cod } } + private void WriteComposedTypePropertyDeserialization2(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) + { + // iterate over all the types and generate a statement per type + // i,e collection of primitives , collection of objects , enum , enum collection , objects + + // start type + writer.Write($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = "); + // loop over all the type + int position = 0; + foreach (var codeType in composedType.Types) + { + if (position > 0) + { + writer.Write(" ?? "); + } + if (!codeType.IsCollection) + { + // if not a collection + if (IsPrimitiveType(TranslateTypescriptType(codeType))) + { + writer.Write($"n.{conventions.GetDeserializationMethodName(codeType, codeFunction.OriginalLocalMethod)}"); + } + else + { + // this is an object / enum + var serializationMethodName = GetObjectTypeSerializationMethodName(codeType, codeFunction); + writer.Write($"n.{serializationMethodName}{GetDefaultValueSuffix(codeProperty)}"); + } + } + else + { + // if is a collection + var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); + if (IsPrimitiveType(TranslateTypescriptType(codeType))) + { + writer.Write($"{GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false)}"); + } + else + { + writer.Write($"n.{GetCollectionOfObjectsSerializationMethodName(codeType, codeFunction)}{defaultValueSuffix}"); + } + } + position++; + } + writer.WriteLine($" }},"); + //writer.WriteLine(";"); + } + + private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) + { + var expression = string.Join(" ?? ", composedType.Types.Select(x => $"n." + conventions.GetDeserializationMethodName(x, codeFunction.OriginalLocalMethod))); + writer.Write($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {expression} }},"); + } + private string GetObjectTypeSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) { var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); return $"getObjectValue<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; } + private string GetObjectTypeSerializationMethodName(CodeType codeType, CodeFunction codeFunction) + { + var propertyType = GetTypescriptTypeString(codeType, codeFunction, false, inlineComposedTypeString: true); + return $"getObjectValue<{propertyType}>({GetFactoryMethodName(codeType, codeFunction)})"; + } + private string GetCollectionOfObjectsSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) { var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); return $"getCollectionOfObjectValues<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; } + private string GetCollectionOfObjectsSerializationMethodName(CodeType codeType, CodeFunction codeFunction) + { + var propertyType = GetTypescriptTypeString(codeType, codeFunction, false, inlineComposedTypeString: true); + return $"getCollectionOfObjectValues<{propertyType}>({GetFactoryMethodName(codeType, codeFunction)})"; + } + private void WriteDefaultPropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeFunction codeFunction, string suffix) { var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); From 305b01b251e887a7b9e58f3f8a38ea1cc193890c Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:26:43 +0300 Subject: [PATCH 099/117] Fix serialization with differenterent types --- .../Writers/TypeScript/CodeFunctionWriter.cs | 122 +----------------- .../TypeScript/TypeScriptConventionService.cs | 4 +- .../TypeScriptRelativeImportManager.cs | 2 +- 3 files changed, 8 insertions(+), 120 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index b82d854ca4..579fe8dbb4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -522,7 +522,6 @@ _ when conventions.StreamTypeName.Equals(propertyType, StringComparison.OrdinalI private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter writer) { - // handle composed types var composedParam = GetComposedTypeParameter(codeFunction); if (composedParam is not null) { @@ -592,11 +591,14 @@ private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParam } else if (GetOriginalComposedType(otherProp.Type) is { } composedType) { - WriteComposedTypePropertyDeserialization(writer, otherProp, paramName, propName, composedType, codeFunction, suffix); + var expression = string.Join(" ?? ", composedType.Types.Select(codeType => $"n.{conventions.GetDeserializationMethodName(codeType, codeFunction.OriginalLocalMethod, composedType.IsCollection)}")); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = {expression};{suffix} }},"); } else { - WriteDefaultPropertyDeserialization(writer, otherProp, paramName, propName, codeFunction, suffix); + var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); + var defaultValueSuffix = GetDefaultValueSuffix(otherProp); + writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = n.{objectSerializationMethodName}{defaultValueSuffix};{suffix} }},"); } } @@ -615,120 +617,6 @@ private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); } - private void WriteComposedTypePropertyDeserialization1(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) - { - // for composed types with a discriminator, - var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); - if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) - { - writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {GetFactoryMethodName(codeProperty.Type, codeFunction)}(n); }},"); - } - else if (composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) - { - var serializationMethodName = composedType.IsCollection || IsComposedOfCollectionOfObjects(composedType) ? GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction) : GetObjectTypeSerializationMethodName(codeProperty, codeFunction); - var primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false); - writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {primitiveValuesUnionString} ?? n.{serializationMethodName}{defaultValueSuffix};{suffix} }},"); - } - else // its composed of objects - { - // check if its all comprised items are a collection - if (IsComposedOfNonCollectionOfObjects(composedType)) - { - var serializationMethodName = composedType.IsCollection ? GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction) : GetObjectTypeSerializationMethodName(codeProperty, codeFunction); - writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = n.{serializationMethodName}{defaultValueSuffix};{suffix} }},"); - } - else if (IsComposedOfCollectionOfObjects(composedType)) - { - writer.WriteLine($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = n.{GetCollectionOfObjectsSerializationMethodName(codeProperty, codeFunction)}{defaultValueSuffix};{suffix} }},"); - } - } - } - - private void WriteComposedTypePropertyDeserialization2(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) - { - // iterate over all the types and generate a statement per type - // i,e collection of primitives , collection of objects , enum , enum collection , objects - - // start type - writer.Write($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = "); - // loop over all the type - int position = 0; - foreach (var codeType in composedType.Types) - { - if (position > 0) - { - writer.Write(" ?? "); - } - if (!codeType.IsCollection) - { - // if not a collection - if (IsPrimitiveType(TranslateTypescriptType(codeType))) - { - writer.Write($"n.{conventions.GetDeserializationMethodName(codeType, codeFunction.OriginalLocalMethod)}"); - } - else - { - // this is an object / enum - var serializationMethodName = GetObjectTypeSerializationMethodName(codeType, codeFunction); - writer.Write($"n.{serializationMethodName}{GetDefaultValueSuffix(codeProperty)}"); - } - } - else - { - // if is a collection - var defaultValueSuffix = GetDefaultValueSuffix(codeProperty); - if (IsPrimitiveType(TranslateTypescriptType(codeType))) - { - writer.Write($"{GetSerializationMethodsForPrimitiveUnionTypes(composedType, "n", codeFunction, false)}"); - } - else - { - writer.Write($"n.{GetCollectionOfObjectsSerializationMethodName(codeType, codeFunction)}{defaultValueSuffix}"); - } - } - position++; - } - writer.WriteLine($" }},"); - //writer.WriteLine(";"); - } - - private void WriteComposedTypePropertyDeserialization(LanguageWriter writer, CodeProperty codeProperty, string paramName, string propName, CodeComposedTypeBase composedType, CodeFunction codeFunction, string suffix) - { - var expression = string.Join(" ?? ", composedType.Types.Select(x => $"n." + conventions.GetDeserializationMethodName(x, codeFunction.OriginalLocalMethod))); - writer.Write($"\"{codeProperty.WireName}\": n => {{ {paramName}.{propName} = {expression} }},"); - } - - private string GetObjectTypeSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) - { - var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); - return $"getObjectValue<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; - } - - private string GetObjectTypeSerializationMethodName(CodeType codeType, CodeFunction codeFunction) - { - var propertyType = GetTypescriptTypeString(codeType, codeFunction, false, inlineComposedTypeString: true); - return $"getObjectValue<{propertyType}>({GetFactoryMethodName(codeType, codeFunction)})"; - } - - private string GetCollectionOfObjectsSerializationMethodName(CodeProperty codeProperty, CodeFunction codeFunction) - { - var propertyType = GetTypescriptTypeString(codeProperty.Type, codeFunction, false, inlineComposedTypeString: true); - return $"getCollectionOfObjectValues<{propertyType}>({GetFactoryMethodName(codeProperty.Type, codeFunction)})"; - } - - private string GetCollectionOfObjectsSerializationMethodName(CodeType codeType, CodeFunction codeFunction) - { - var propertyType = GetTypescriptTypeString(codeType, codeFunction, false, inlineComposedTypeString: true); - return $"getCollectionOfObjectValues<{propertyType}>({GetFactoryMethodName(codeType, codeFunction)})"; - } - - private void WriteDefaultPropertyDeserialization(LanguageWriter writer, CodeProperty otherProp, string paramName, string propName, CodeFunction codeFunction, string suffix) - { - var objectSerializationMethodName = conventions.GetDeserializationMethodName(otherProp.Type, codeFunction.OriginalLocalMethod); - var defaultValueSuffix = GetDefaultValueSuffix(otherProp); - writer.WriteLine($"\"{otherProp.WireName}\": n => {{ {paramName}.{propName} = n.{objectSerializationMethodName}{defaultValueSuffix};{suffix} }},"); - } - private string GetDefaultValueSuffix(CodeProperty otherProp) { var defaultValue = GetDefaultValueLiteralForProperty(otherProp); diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 950e4450de..8bf9d0c213 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -292,11 +292,11 @@ public static string GetFactoryMethodName(CodeTypeBase targetClassType, CodeElem return definitionClass.GetImmediateParentOfType(definitionClass)?.FindChildByName(factoryMethodName); } - public string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method) + public string GetDeserializationMethodName(CodeTypeBase codeType, CodeMethod method, bool? IsCollection = null) { ArgumentNullException.ThrowIfNull(codeType); ArgumentNullException.ThrowIfNull(method); - var isCollection = codeType.CollectionKind != CodeTypeCollectionKind.None; + var isCollection = IsCollection == true || codeType.IsCollection; var propertyType = GetTypescriptTypeString(codeType, method, false); CodeTypeBase _codeType = GetOriginalComposedType(codeType) is CodeComposedTypeBase composedType ? new CodeType() { Name = composedType.Name, TypeDefinition = composedType } : codeType; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs index 98d310169d..8006f55698 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs @@ -4,6 +4,7 @@ namespace Kiota.Builder.Writers.TypeScript; public class TypescriptRelativeImportManager(string namespacePrefix, char namespaceSeparator) : RelativeImportManager(namespacePrefix, namespaceSeparator) { + private const string IndexFileName = "index.js"; /// /// Returns the relative import path for the given using and import context namespace. /// @@ -31,5 +32,4 @@ public override (string, string, string) GetRelativeImportPathForUsing(CodeUsing importPath += ".js"; return (importSymbol, codeUsing.Alias, importPath); } - private const string IndexFileName = "index.js"; } From 429d1a9d1fbd56d9e0105e267ea354ac12418ace Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:47:14 +0300 Subject: [PATCH 100/117] Fixes import collition for types --- .../Refiners/TypeScriptRefiner.cs | 33 +++++++++++++++++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 1 - 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 3c251a4a4e..8f60caa899 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -177,9 +177,42 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance GenerateRequestBuilderCodeFiles(modelsNamespace); GroupReusableModelsInSingleFile(modelsNamespace); RemoveSelfReferencingUsings(generatedCode); + AddAliasToCodeFileUsings(generatedCode); cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } + + private static void AddAliasToCodeFileUsings(CodeElement currentElement) + { + if (currentElement is CodeFile codeFile) + { + var enumeratedUsings = codeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement).ToArray(); + var duplicatedUsings = enumeratedUsings.Where(static x => !x.IsExternal) + .Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null) + .Where(static x => string.IsNullOrEmpty(x.Alias)) + .GroupBy(static x => x.Declaration!.Name, StringComparer.OrdinalIgnoreCase) + .Where(static x => x.Count() > 1) + .Where(static x => x.DistinctBy(static y => y.Declaration!.TypeDefinition!.GetImmediateParentOfType()) + .Count() > 1) + .SelectMany(static x => x) + .ToArray(); + + if(duplicatedUsings.Length > 0) + foreach (var usingElement in duplicatedUsings) + usingElement.Alias = (usingElement.Declaration + ?.TypeDefinition + ?.GetImmediateParentOfType() + .Name + + usingElement.Declaration + ?.TypeDefinition + ?.Name.ToFirstCharacterUpperCase()) + .GetNamespaceImportSymbol() + .ToFirstCharacterUpperCase(); + } + + CrawlTree(currentElement, AddAliasToCodeFileUsings); + } + private static void GenerateEnumObjects(CodeElement currentElement) { AddEnumObject(currentElement); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 579fe8dbb4..570cf5c889 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -3,7 +3,6 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -using Kiota.Builder.Writers.Go; using static Kiota.Builder.Refiners.TypeScriptRefiner; using static Kiota.Builder.Writers.TypeScript.TypeScriptConventionService; From b6ac6b386e694fa84cd4ead906ce66418c5b7f86 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Fri, 23 Aug 2024 06:22:20 +0300 Subject: [PATCH 101/117] Fix import types --- src/Kiota.Builder/CodeDOM/CodeElement.cs | 16 +++++++++--- src/Kiota.Builder/CodeDOM/CodeFile.cs | 4 ++- .../Refiners/TypeScriptRefiner.cs | 12 ++++----- .../Writers/TypeScript/CodeFunctionWriter.cs | 19 ++++++-------- .../TypeScript/TypeScriptConventionService.cs | 26 +++++++++++++------ 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeElement.cs b/src/Kiota.Builder/CodeDOM/CodeElement.cs index 76230b5670..50d23303e4 100644 --- a/src/Kiota.Builder/CodeDOM/CodeElement.cs +++ b/src/Kiota.Builder/CodeDOM/CodeElement.cs @@ -33,17 +33,25 @@ protected void EnsureElementsAreChildren(params ICodeElement?[] elements) foreach (var element in elements.Where(x => x != null && (x.Parent == null || x.Parent != this))) element!.Parent = this; } - public T GetImmediateParentOfType(CodeElement? item = null) + + public T GetImmediateParentOfType(CodeElement? item = null) => + GetImmediateParentOfTypeOrDefault(item, + e => throw new InvalidOperationException($"item {e.Name} of type {e.GetType()} does not have a parent"))!; + + public T? GetImmediateParentOfTypeOrDefault(CodeElement? item = null, Action? onFail = null) { if (item == null) - return GetImmediateParentOfType(this); + return GetImmediateParentOfTypeOrDefault(this); if (item is T p) return p; if (item.Parent == null) - throw new InvalidOperationException($"item {item.Name} of type {item.GetType()} does not have a parent"); + { + onFail?.Invoke(item); + return default; + } if (item.Parent is T p2) return p2; - return GetImmediateParentOfType(item.Parent); + return GetImmediateParentOfTypeOrDefault(item.Parent); } public bool IsChildOf(CodeElement codeElement, bool immediateOnly = false) { diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index f823c8e8cd..41f09351db 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -24,7 +24,9 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement public IEnumerable AllUsingsFromChildElements => GetChildElements(true) .SelectMany(static x => x.GetChildElements(false)) .OfType() - .SelectMany(static x => x.Usings); + .SelectMany(static x => x.Usings) + .Union(GetChildElements(true).Where(x => x is CodeConstant).Cast() + .SelectMany(static x => x.StartBlock.Usings)); } public class CodeFileDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 8f60caa899..e5d30323ab 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -181,11 +181,11 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } - + private static void AddAliasToCodeFileUsings(CodeElement currentElement) { if (currentElement is CodeFile codeFile) - { + { var enumeratedUsings = codeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement).ToArray(); var duplicatedUsings = enumeratedUsings.Where(static x => !x.IsExternal) .Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null) @@ -196,8 +196,8 @@ private static void AddAliasToCodeFileUsings(CodeElement currentElement) .Count() > 1) .SelectMany(static x => x) .ToArray(); - - if(duplicatedUsings.Length > 0) + + if (duplicatedUsings.Length > 0) foreach (var usingElement in duplicatedUsings) usingElement.Alias = (usingElement.Declaration ?.TypeDefinition @@ -212,7 +212,7 @@ private static void AddAliasToCodeFileUsings(CodeElement currentElement) CrawlTree(currentElement, AddAliasToCodeFileUsings); } - + private static void GenerateEnumObjects(CodeElement currentElement) { AddEnumObject(currentElement); @@ -577,7 +577,7 @@ private static void GenerateRequestBuilderCodeFile(CodeInterface codeInterface, codeNamespace.TryAddCodeFile(codeInterface.Name, elements); } - private static IEnumerable GetUsingsFromCodeElement(CodeElement codeElement) + public static IEnumerable GetUsingsFromCodeElement(CodeElement codeElement) { return codeElement switch { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 570cf5c889..561d7158fb 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -330,9 +330,16 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind) { - var functionName = CreateSerializationFunctionNameFromType(returnType, kind); + var functionName = kind switch + { + CodeMethodKind.Serializer => $"serialize{returnType}", + CodeMethodKind.Deserializer => $"deserializeInto{returnType}", + _ => throw new InvalidOperationException($"Unsupported function kind :: {kind}") + }; + var parentNamespace = codeElement.GetImmediateParentOfType(); var codeFunction = FindCodeFunctionInParentNamespaces(functionName, parentNamespace); + return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); } @@ -350,16 +357,6 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM return codeFunction; } - private static string CreateSerializationFunctionNameFromType(string returnType, CodeMethodKind functionKind) - { - return functionKind switch - { - CodeMethodKind.Serializer => $"serialize{returnType}", - CodeMethodKind.Deserializer => $"deserializeInto{returnType}", - _ => throw new InvalidOperationException($"Unsupported function kind :: {functionKind}") - }; - } - private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) { // Determine if the function serializes a composed type diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 8bf9d0c213..3c6a45c933 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -166,14 +166,24 @@ private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTy private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { - if (targetElement.GetImmediateParentOfType() is IBlock parentBlock && - parentBlock.Usings - .FirstOrDefault(x => !x.IsExternal && - x.Declaration?.TypeDefinition != null && - x.Declaration.TypeDefinition == targetType.TypeDefinition && - !string.IsNullOrEmpty(x.Alias)) is CodeUsing aliasedUsing) - return aliasedUsing.Alias; - return string.Empty; + if (targetElement is CodeFile codeFile) + return GetTypeAlias(targetType, codeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement)); + if (targetElement.GetImmediateParentOfTypeOrDefault() is CodeFile parentCodeFile) + return GetTypeAlias(targetType, parentCodeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement)); + if (targetElement.GetImmediateParentOfType() is IBlock parentBlock) + return GetTypeAlias(targetType, parentBlock.Usings); + + return GetTypeAlias(targetType, Array.Empty()); + } + + private static string GetTypeAlias(CodeType targetType, IEnumerable usings) + { + var aliasedUsing = usings.FirstOrDefault(x => !x.IsExternal && + x.Declaration?.TypeDefinition != null && + x.Declaration.TypeDefinition == targetType.TypeDefinition && + !string.IsNullOrEmpty(x.Alias)); + + return aliasedUsing != null ? aliasedUsing.Alias : string.Empty; } public override string TranslateType(CodeType type) From fbdb5692a9eed67caef4978282f35499d470a0ff Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:18:23 +0300 Subject: [PATCH 102/117] Fix import --- .../CodeDOM/CodeComposedTypeBase.cs | 7 ++ .../Refiners/TypeScriptRefiner.cs | 43 +++++++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 88 ++++++++----------- 3 files changed, 76 insertions(+), 62 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index f12a127ae6..6b4e491b66 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -26,6 +26,13 @@ public bool ContainsType(CodeType codeType) ArgumentNullException.ThrowIfNull(codeType); return types.ContainsKey(NormalizeKey(codeType)); } + public void SetTypes(params CodeType[] codeTypes) + { + ArgumentNullException.ThrowIfNull(codeTypes); + types.Clear(); + foreach (var codeType in codeTypes) + AddType(codeType); + } private readonly ConcurrentDictionary types = new(StringComparer.OrdinalIgnoreCase); public IEnumerable Types { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index e5d30323ab..aa89fc877e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -178,9 +178,33 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance GroupReusableModelsInSingleFile(modelsNamespace); RemoveSelfReferencingUsings(generatedCode); AddAliasToCodeFileUsings(generatedCode); + CorrectSerializerParameters(generatedCode); cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } + + private static void CorrectSerializerParameters(CodeElement currentElement) + { + if (currentElement is CodeFunction currentFunction && + currentFunction.OriginalLocalMethod.Kind is CodeMethodKind.Serializer) + { + + foreach (var parameter in currentFunction.OriginalLocalMethod.Parameters) + { + if (GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && + composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) + { + var newType = (composedType.Clone() as CodeComposedTypeBase)!; + var nonPrimitiveTypes = composedType.Types.Where(x => !IsComposedPrimitive(x, composedType)).ToArray(); + newType.SetTypes(nonPrimitiveTypes); + parameter.Type = newType; + } + + } + } + + CrawlTree(currentElement, CorrectSerializerParameters); + } private static void AddAliasToCodeFileUsings(CodeElement currentElement) { @@ -380,16 +404,17 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase private static void AddSerializationUsingsForCodeComposed(CodeComposedTypeBase composedType, CodeFunction function, CodeMethodKind kind) { // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces - foreach (var type in composedType.GetNonPrimitiveTypes(IsComposedPrimitive)) + foreach (var codeClass in composedType.GetNonPrimitiveTypes(IsComposedPrimitive) + .Select(static x => x.TypeDefinition) + .OfType() + .Select(static x => x.OriginalClass) + .OfType()) { - if (type.TypeDefinition is CodeInterface codeInterface && codeInterface.OriginalClass is CodeClass codeClass) - { - var (serializer, deserializer) = GetSerializationFunctionsForNamespace(codeClass); - if (kind == CodeMethodKind.Serializer) - AddSerializationUsingsToFunction(function, serializer); - if (kind == CodeMethodKind.Deserializer) - AddSerializationUsingsToFunction(function, deserializer); - } + var (serializer, deserializer) = GetSerializationFunctionsForNamespace(codeClass); + if (kind == CodeMethodKind.Serializer) + AddSerializationUsingsToFunction(function, serializer); + if (kind == CodeMethodKind.Deserializer) + AddSerializationUsingsToFunction(function, deserializer); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 561d7158fb..cc20beccc4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -92,7 +92,14 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) { - WriteSerializationFunctionForTypeComposedOfPrimitives(composedType, composedParam, codeElement, writer); + var paramName = composedParam.Name.ToFirstCharacterLowerCase(); + writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;"); + writer.StartBlock($"switch (typeof {paramName}) {{"); + foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) + { + WriteCaseStatementForPrimitiveTypeSerialization(type, "key", paramName, codeElement, writer); + } + writer.CloseBlock(); return; } @@ -115,24 +122,6 @@ private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeB } } - public static bool IsComposedOfCollectionOfObjects(CodeComposedTypeBase composedType) - { - ArgumentNullException.ThrowIfNull(composedType); - // Get the objects - var composedTypeObjects = composedType.Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, composedType))); - // Get the collections count - return composedTypeObjects.Any(x => x.IsCollection); - } - - public static bool IsComposedOfNonCollectionOfObjects(CodeComposedTypeBase composedType) - { - ArgumentNullException.ThrowIfNull(composedType); - // Get the objects - var composedTypeObjects = composedType.Types.Where(x => !IsPrimitiveType(GetTypescriptTypeString(x, composedType))); - // Get the non collections count - return composedTypeObjects.Any(x => !x.IsCollection); - } - private void WriteSerializationFunctionForCodeUnionTypes(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction codeElement, LanguageWriter writer) { var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; @@ -186,20 +175,6 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato writer.CloseBlock(); } - private void WriteSerializationFunctionForTypeComposedOfPrimitives(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) - { - var paramName = composedParam.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;"); - writer.StartBlock($"switch (typeof {paramName}) {{"); - - foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) - { - WriteCaseStatementForPrimitiveTypeSerialization(type, "key", paramName, method, writer); - } - - writer.CloseBlock(); - } - private void WriteCaseStatementForPrimitiveTypeSerialization(CodeTypeBase type, string key, string value, CodeFunction method, LanguageWriter writer) { var nodeType = conventions.GetTypeString(type, method, false); @@ -433,46 +408,55 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); - var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; - writer.StartBlock($"switch (typeof {modelParamName}.{codePropertyName}) {{"); + writer.StartBlock("switch (true) {"); - foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) + foreach (var type in composedType.Types.Where(x => IsComposedPrimitive(x, composedType))) { - WriteCaseStatementForPrimitiveTypeSerialization(type, $"\"{codeProperty.WireName}\"", $"{spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix}", method, writer); + var nodeType = conventions.GetTypeString(type, method, false); + var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod); + if (string.IsNullOrEmpty(serializationName) || string.IsNullOrEmpty(nodeType)) return; + + writer.StartBlock(type.IsCollection + ? $"Array.isArray({modelParamName}.{codePropertyName}) && ({modelParamName}.{codePropertyName}).every(item => typeof item === '{nodeType}') :" + : $"case typeof {modelParamName}.{codePropertyName} === \"{nodeType}\":"); + + writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix} as {nodeType});"); + writer.CloseBlock("break;"); } - if (composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) + var nonPrimitiveTypes = composedType.Types.Where(x => !IsComposedPrimitive(x, composedType)).ToArray(); + if (nonPrimitiveTypes.Length > 0) { - // write the default statement serialization statement for the object - writer.StartBlock($"default:"); - if (IsComposedOfCollectionOfObjects(composedType)) + writer.StartBlock("default:"); + foreach (var groupedTypes in nonPrimitiveTypes.GroupBy(static x => x.IsCollection)) { - writer.WriteLine($"writer.writeCollectionOfObjectValues<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); + var collectionCodeType = (composedType.Clone() as CodeComposedTypeBase)!; + collectionCodeType.SetTypes(groupedTypes.ToArray()); + var propTypeName = GetTypescriptTypeString(collectionCodeType!, codeProperty.Parent!, false, inlineComposedTypeString: true); + + var writerFunction = groupedTypes.Key ? "writeCollectionOfObjectValues" : "writeObjectValue"; + var propertyTypes = collectionCodeType.IsNullable ? " | undefined | null" : string.Empty; + var groupSymbol = groupedTypes.Key ? "[]" : string.Empty; + + writer.WriteLine($"writer.{writerFunction}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix} as {propTypeName}{groupSymbol}{propertyTypes}, {serializeName});"); } - if (IsComposedOfNonCollectionOfObjects(composedType)) - { - writer.WriteLine($"writer.writeObjectValue<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); - } - writer.WriteLine($"break;"); - writer.DecreaseIndent(); + writer.CloseBlock("break;"); } writer.CloseBlock(); } - public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) + private string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { ArgumentNullException.ThrowIfNull(propertyType); ArgumentNullException.ThrowIfNull(method); var composedType = GetOriginalComposedType(propertyType); if (composedType is not null && composedType.IsComposedOfPrimitives(IsComposedPrimitive)) - { return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; - } var propertyTypeName = TranslateTypescriptType(propertyType); CodeType? currentType = composedType is not null ? GetCodeTypeForComposedType(composedType) : propertyType as CodeType; @@ -487,9 +471,7 @@ public string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod m } if (propertyTypeName is TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_BOOLEAN or TYPE_NUMBER or TYPE_GUID or TYPE_DATE or TYPE_DATE_ONLY or TYPE_TIME_ONLY or TYPE_DURATION) - { return $"write{propertyTypeName.ToFirstCharacterUpperCase()}Value"; - } return "writeObjectValue"; } From 940017e9a6bfa9984bc97e2fa58244a0b3b8bd9e Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 01:22:30 +0300 Subject: [PATCH 103/117] Fix unit tests --- .../Refiners/TypeScriptRefiner.cs | 6 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 57 ++++++++++++------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index aa89fc877e..9b666535d4 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -182,13 +182,13 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } - + private static void CorrectSerializerParameters(CodeElement currentElement) { if (currentElement is CodeFunction currentFunction && currentFunction.OriginalLocalMethod.Kind is CodeMethodKind.Serializer) { - + foreach (var parameter in currentFunction.OriginalLocalMethod.Parameters) { if (GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && @@ -202,7 +202,7 @@ private static void CorrectSerializerParameters(CodeElement currentElement) } } - + CrawlTree(currentElement, CorrectSerializerParameters); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index cc20beccc4..242fb28918 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -77,11 +77,14 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri if (GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); - // Serialization/Deserialization functions can be called for object types only foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) { - var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"...{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Deserializer)}({composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, codeElement, includeCollectionInformation: false, inlineComposedTypeString: true)}),"); + var functionName = GetDeserializerFunctionName(codeElement, mappedType); + var variableName = composedParam.Name.ToFirstCharacterLowerCase(); + var variableType = GetTypescriptTypeString(mappedType, codeElement, includeCollectionInformation: false, + inlineComposedTypeString: true); + + writer.WriteLine($"...{functionName}({variableName} as {variableType}),"); } writer.CloseBlock(); } @@ -117,8 +120,11 @@ private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeB // Serialization/Deserialization functions can be called for object types only foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) { - var mappedTypeName = mappedType.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{GetFunctionName(method, mappedTypeName, CodeMethodKind.Serializer)}(writer, {composedParam.Name.ToFirstCharacterLowerCase()} as {GetTypescriptTypeString(mappedType, method, includeCollectionInformation: false, inlineComposedTypeString: true)});"); + var functionName = GetSerializerFunctionName(method, mappedType); + var variableName = composedParam.Name.ToFirstCharacterLowerCase(); + var variableType = GetTypescriptTypeString(mappedType, method, includeCollectionInformation: false, inlineComposedTypeString: true); + + writer.WriteLine($"{functionName}(writer, {variableName} as {variableType});"); } } @@ -166,8 +172,7 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato foreach (var mappedType in discriminatorInfo.DiscriminatorMappings) { writer.StartBlock($"case \"{mappedType.Key}\":"); - var mappedTypeName = mappedType.Value.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{GetFunctionName(codeElement, mappedTypeName, CodeMethodKind.Serializer)}(writer, {paramName} as {mappedType.Value.AllTypes.First().Name.ToFirstCharacterUpperCase()});"); + writer.WriteLine($"{GetSerializerFunctionName(codeElement, mappedType.Value)}(writer, {paramName} as {mappedType.Value.AllTypes.First().Name.ToFirstCharacterUpperCase()});"); writer.WriteLine("break;"); writer.DecreaseIndent(); } @@ -258,7 +263,7 @@ private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, st private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) { var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); - if (ShouldWriteDiscriminatorInformation(codeElement, null) && parseNodeParameter != null) + if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType && parseNodeParameter != null) { WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); } @@ -267,15 +272,11 @@ private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string retur private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer) { - var deserializationFunction = GetFunctionName(codeElement, returnType, CodeMethodKind.Deserializer); + var nameSpace = codeElement.GetImmediateParentOfType(); + var deserializationFunction = GetFunctionName(codeElement, returnType, CodeMethodKind.Deserializer, nameSpace); writer.WriteLine($"return {deserializationFunction.ToFirstCharacterLowerCase()};"); } - private static bool ShouldWriteDiscriminatorInformation(CodeFunction codeElement, CodeComposedTypeBase? composedType) - { - return codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType || composedType is CodeUnionType; - } - private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParameter parseNodeParameter, LanguageWriter writer) { var discriminatorInfo = codeElement.OriginalMethodParentClass.DiscriminatorInformation; @@ -294,7 +295,7 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet foreach (var mappedType in discriminatorInfo.DiscriminatorMappings) { writer.StartBlock($"case \"{mappedType.Key}\":"); - writer.WriteLine($"return {GetFunctionName(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase(), CodeMethodKind.Deserializer)};"); + writer.WriteLine($"return {GetDeserializerFunctionName(codeElement, mappedType.Value)};"); writer.DecreaseIndent(); } writer.CloseBlock(); @@ -303,7 +304,7 @@ private void WriteDiscriminatorInformation(CodeFunction codeElement, CodeParamet } } - private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind) + private string GetFunctionName(CodeElement codeElement, string returnType, CodeMethodKind kind, CodeNamespace targetNamespace) { var functionName = kind switch { @@ -312,8 +313,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM _ => throw new InvalidOperationException($"Unsupported function kind :: {kind}") }; - var parentNamespace = codeElement.GetImmediateParentOfType(); - var codeFunction = FindCodeFunctionInParentNamespaces(functionName, parentNamespace); + var codeFunction = FindCodeFunctionInParentNamespaces(functionName, targetNamespace); return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); } @@ -332,6 +332,23 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM return codeFunction; } + private string GetDeserializerFunctionName(CodeFunction currentFunction, CodeType returnType) => + FindFunctionInNameSpace($"deserializeInto{returnType.Name.ToFirstCharacterUpperCase()}", currentFunction, returnType); + + private string GetSerializerFunctionName(CodeFunction currentFunction, CodeType returnType) => + FindFunctionInNameSpace($"serialize{returnType.Name.ToFirstCharacterUpperCase()}", currentFunction, returnType); + + private string FindFunctionInNameSpace(string functionName, CodeFunction currentFunction, CodeType returnType) + { + var myNamespace = returnType.TypeDefinition!.GetImmediateParentOfType(); + + CodeFunction? codeFunction = myNamespace.FindChildByName(functionName); + if (codeFunction == null) + throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace?.Name}"); + + return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, currentFunction, false); + } + private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) { // Determine if the function serializes a composed type @@ -436,11 +453,11 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType var collectionCodeType = (composedType.Clone() as CodeComposedTypeBase)!; collectionCodeType.SetTypes(groupedTypes.ToArray()); var propTypeName = GetTypescriptTypeString(collectionCodeType!, codeProperty.Parent!, false, inlineComposedTypeString: true); - + var writerFunction = groupedTypes.Key ? "writeCollectionOfObjectValues" : "writeObjectValue"; var propertyTypes = collectionCodeType.IsNullable ? " | undefined | null" : string.Empty; var groupSymbol = groupedTypes.Key ? "[]" : string.Empty; - + writer.WriteLine($"writer.{writerFunction}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix} as {propTypeName}{groupSymbol}{propertyTypes}, {serializeName});"); } writer.CloseBlock("break;"); From 9ef966b111ed3f2a027e048845f1b54a11a5bc51 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 08:02:28 +0300 Subject: [PATCH 104/117] minor refactr --- .../CodeDOM/CodeComposedTypeBase.cs | 11 --------- .../Refiners/TypeScriptRefiner.cs | 12 +++++----- .../Writers/TypeScript/CodeFunctionWriter.cs | 24 +++++++++---------- .../TypeScript/TypeScriptConventionService.cs | 18 +++++++------- .../TypeScriptConventionServiceTests.cs | 12 +++++----- 5 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index 6b4e491b66..c7ba592eca 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -91,15 +91,4 @@ public bool IsComposedOfObjectsAndPrimitives(Func 0 && primitiveCount < Types.Count(); } - public IEnumerable GetPrimitiveTypes(Func checkIfPrimitive) - { - // Return only the primitive types from the Types collection - return Types.Where(x => checkIfPrimitive(x, this)); - } - - public IEnumerable GetNonPrimitiveTypes(Func checkIfPrimitive) - { - // Return only the non primitive types from the Types collection - return Types.Where(x => !checkIfPrimitive(x, this)); - } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 9b666535d4..99ea262797 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -192,10 +192,10 @@ private static void CorrectSerializerParameters(CodeElement currentElement) foreach (var parameter in currentFunction.OriginalLocalMethod.Parameters) { if (GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && - composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive)) + composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType)) { var newType = (composedType.Clone() as CodeComposedTypeBase)!; - var nonPrimitiveTypes = composedType.Types.Where(x => !IsComposedPrimitive(x, composedType)).ToArray(); + var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); newType.SetTypes(nonPrimitiveTypes); parameter.Type = newType; } @@ -381,7 +381,7 @@ private static void ReplaceFactoryMethodForComposedType(CodeComposedTypeBase com { if (composedType is null || FindFunctionOfKind(children, CodeMethodKind.Factory) is not { } function) return; - if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) + if (composedType.IsComposedOfPrimitives(IsPrimitiveType)) { function.OriginalLocalMethod.ReturnType = composedType; // Remove the deserializer import statement if its not being used @@ -394,7 +394,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase if (FindFunctionOfKind(children, CodeMethodKind.Serializer) is not { } function) return; // Add the key parameter if the composed type is a union of primitive values - if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) + if (composedType.IsComposedOfPrimitives(IsPrimitiveType)) function.OriginalLocalMethod.AddParameter(CreateKeyParameter()); // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces @@ -404,7 +404,7 @@ private static void ReplaceSerializerMethodForComposedType(CodeComposedTypeBase private static void AddSerializationUsingsForCodeComposed(CodeComposedTypeBase composedType, CodeFunction function, CodeMethodKind kind) { // Add code usings for each individual item since the functions can be invoked to serialize/deserialize the contained classes/interfaces - foreach (var codeClass in composedType.GetNonPrimitiveTypes(IsComposedPrimitive) + foreach (var codeClass in composedType.Types.Where(x => !IsPrimitiveType(x, composedType)) .Select(static x => x.TypeDefinition) .OfType() .Select(static x => x.OriginalClass) @@ -439,7 +439,7 @@ private static void ReplaceDeserializerMethodForComposedType(CodeInterface codeI if (FindFunctionOfKind(children, CodeMethodKind.Deserializer) is not { } deserializerMethod) return; // Deserializer function is not required for primitive values - if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) + if (composedType.IsComposedOfPrimitives(IsPrimitiveType)) { children.Remove(deserializerMethod); codeInterface.RemoveChildElement(deserializerMethod); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 242fb28918..fec074330c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -23,7 +23,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; var composedType = GetOriginalComposedType(codeMethod.ReturnType); - var isComposedOfPrimitives = composedType is not null && composedType.IsComposedOfPrimitives(IsComposedPrimitive); + var isComposedOfPrimitives = composedType is not null && composedType.IsComposedOfPrimitives(IsPrimitiveType); var returnType = codeMethod.Kind is CodeMethodKind.Factory && !isComposedOfPrimitives ? FactoryMethodReturnType : @@ -56,7 +56,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w private string GetSerializationMethodsForPrimitiveUnionTypes(CodeComposedTypeBase composedType, string parseNodeParameterName, CodeFunction codeElement, bool nodeParameterCanBeNull = true) { var optionalChainingSymbol = nodeParameterCanBeNull ? "?" : string.Empty; - return string.Join(" ?? ", composedType.GetPrimitiveTypes(IsComposedPrimitive).Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); + return string.Join(" ?? ", composedType.Types.Where(x => IsPrimitiveType(x, composedType)).Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); } private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) @@ -77,7 +77,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri if (GetOriginalComposedType(composedParam) is not { } composedType) return; writer.StartBlock("return {"); - foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) + foreach (var mappedType in composedType.Types.Where(x => !IsPrimitiveType(x, composedType))) { var functionName = GetDeserializerFunctionName(codeElement, mappedType); var variableName = composedParam.Name.ToFirstCharacterLowerCase(); @@ -93,12 +93,12 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite { if (GetOriginalComposedType(composedParam) is not { } composedType) return; - if (composedType.IsComposedOfPrimitives(IsComposedPrimitive)) + if (composedType.IsComposedOfPrimitives(IsPrimitiveType)) { var paramName = composedParam.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;"); writer.StartBlock($"switch (typeof {paramName}) {{"); - foreach (var type in composedType.GetPrimitiveTypes(IsComposedPrimitive)) + foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType))) { WriteCaseStatementForPrimitiveTypeSerialization(type, "key", paramName, codeElement, writer); } @@ -118,7 +118,7 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite private void WriteSerializationFunctionForCodeIntersectionType(CodeComposedTypeBase composedType, CodeParameter composedParam, CodeFunction method, LanguageWriter writer) { // Serialization/Deserialization functions can be called for object types only - foreach (var mappedType in composedType.GetNonPrimitiveTypes(IsComposedPrimitive).ToArray()) + foreach (var mappedType in composedType.Types.Where(x => !IsPrimitiveType(x, composedType))) { var functionName = GetSerializerFunctionName(method, mappedType); var variableName = composedParam.Name.ToFirstCharacterLowerCase(); @@ -238,7 +238,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsComposedPrimitive): + case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsPrimitiveType): WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); break; case CodeUnionType _ when parseNodeParameter != null: @@ -404,7 +404,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null) { var serializeName = GetSerializerAlias(propType, codeFunction, $"serialize{propType.TypeDefinition.Name}"); - if (GetOriginalComposedType(propType.TypeDefinition) is { } ct && (ct.IsComposedOfPrimitives(IsComposedPrimitive) || ct.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive))) + if (GetOriginalComposedType(propType.TypeDefinition) is { } ct && (ct.IsComposedOfPrimitives(IsPrimitiveType) || ct.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) WriteSerializationStatementForComposedTypeProperty(ct, modelParamName, codeFunction, writer, codeProperty, serializeName); else writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); @@ -413,7 +413,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { if (!string.IsNullOrWhiteSpace(spreadOperator)) writer.WriteLine($"if({modelParamName}.{codePropertyName})"); - if (composedType is not null && (composedType.IsComposedOfPrimitives(IsComposedPrimitive) || composedType.IsComposedOfObjectsAndPrimitives(IsComposedPrimitive))) + if (composedType is not null && (composedType.IsComposedOfPrimitives(IsPrimitiveType) || composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty); else writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); @@ -430,7 +430,7 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType writer.StartBlock("switch (true) {"); - foreach (var type in composedType.Types.Where(x => IsComposedPrimitive(x, composedType))) + foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType))) { var nodeType = conventions.GetTypeString(type, method, false); var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod); @@ -444,7 +444,7 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType writer.CloseBlock("break;"); } - var nonPrimitiveTypes = composedType.Types.Where(x => !IsComposedPrimitive(x, composedType)).ToArray(); + var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); if (nonPrimitiveTypes.Length > 0) { writer.StartBlock("default:"); @@ -472,7 +472,7 @@ private string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod ArgumentNullException.ThrowIfNull(method); var composedType = GetOriginalComposedType(propertyType); - if (composedType is not null && composedType.IsComposedOfPrimitives(IsComposedPrimitive)) + if (composedType is not null && composedType.IsComposedOfPrimitives(IsPrimitiveType)) return $"serialize{composedType.Name.ToFirstCharacterUpperCase()}"; var propertyTypeName = TranslateTypescriptType(propertyType); diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 3c6a45c933..092827c6e8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -92,7 +92,7 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen ArgumentNullException.ThrowIfNull(parameter); var includeCollectionInformation = ShouldIncludeCollectionInformationForParameter(parameter); var paramType = GetTypescriptTypeString(parameter.Type, targetElement, includeCollectionInformation: includeCollectionInformation, inlineComposedTypeString: true); - var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(IsComposedPrimitive); + var isComposedOfPrimitives = GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfPrimitives(IsPrimitiveType); var defaultValueSuffix = (string.IsNullOrEmpty(parameter.DefaultValue), parameter.Kind, isComposedOfPrimitives) switch { (false, CodeParameterKind.DeserializationTarget, false) when parameter.Parent is CodeMethod codeMethod && codeMethod.Kind is CodeMethodKind.Serializer @@ -154,16 +154,16 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ */ private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix, bool includeCollectionInformation = true) { - if (!composedType.Types.Any()) throw new InvalidOperationException($"Composed type should be comprised of at least one type"); - var returnTypeString = string.Join(GetTypesDelimiterToken(composedType), composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation: includeCollectionInformation))); + if (!composedType.Types.Any()) + throw new InvalidOperationException($"Composed type should be comprised of at least one type"); + + var typesDelimiter = composedType is CodeUnionType or CodeIntersectionType ? " | " : + throw new InvalidOperationException("Unknown composed type"); + + var returnTypeString = string.Join(typesDelimiter, composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation: includeCollectionInformation))); return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; } - private static string GetTypesDelimiterToken(CodeComposedTypeBase codeComposedTypeBase) - { - return codeComposedTypeBase is CodeUnionType or CodeIntersectionType ? " | " : throw new InvalidOperationException("Unknown composed type"); - } - private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { if (targetElement is CodeFile codeFile) @@ -229,7 +229,7 @@ TYPE_LOWERCASE_BOOLEAN or }; } - public static bool IsComposedPrimitive(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(GetTypescriptTypeString(codeType, codeComposedTypeBase)); + public static bool IsPrimitiveType(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(GetTypescriptTypeString(codeType, codeComposedTypeBase)); internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription?.Replace("\\", "/", StringComparison.OrdinalIgnoreCase) ?? string.Empty; public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index a00a8699e5..0adf934337 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -38,7 +38,7 @@ public void IsComposedOfPrimitives_ShouldBeTrue_WhenComposedOfPrimitives() var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; composedType.AddType(new CodeType { Name = "string", IsExternal = true }); composedType.AddType(new CodeType { Name = "integer", IsExternal = true }); - Assert.True(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsComposedPrimitive)); + Assert.True(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsPrimitiveType)); } [Fact] @@ -48,7 +48,7 @@ public void IsComposedOfPrimitives_ShouldBeFalse_WhenNotComposedOfPrimitives() composedType.AddType(new CodeType { Name = "string", IsExternal = true }); var td = new CodeClass { Name = "SomeClass" }; composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); - Assert.False(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsComposedPrimitive)); + Assert.False(composedType.IsComposedOfPrimitives(TypeScriptConventionService.IsPrimitiveType)); } [Fact] @@ -60,7 +60,7 @@ public void IsComposedOfObjectsAndPrimitives_OnlyPrimitives_ReturnsFalse() composedType.AddType(new CodeType { Name = "string", IsExternal = true }); // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsPrimitiveType); // Assert Assert.False(result); @@ -75,7 +75,7 @@ public void IsComposedOfObjectsAndPrimitives_OnlyObjects_ReturnsFalse() composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsPrimitiveType); // Assert Assert.False(result); @@ -91,7 +91,7 @@ public void IsComposedOfObjectsAndPrimitives_BothPrimitivesAndObjects_ReturnsTru composedType.AddType(new CodeType { Name = "SomeCustomObject", IsExternal = false, TypeDefinition = td }); // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsPrimitiveType); // Assert Assert.True(result); @@ -104,7 +104,7 @@ public void IsComposedOfObjectsAndPrimitives_EmptyTypes_ReturnsFalse() var composedType = new CodeUnionType { Name = "test", Parent = CurrentType() }; // Act - var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsComposedPrimitive); + var result = composedType.IsComposedOfObjectsAndPrimitives(TypeScriptConventionService.IsPrimitiveType); // Assert Assert.False(result); From 2b7f992757f27ae5f4acba9c94ad7116c506b5ee Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:12:16 +0300 Subject: [PATCH 105/117] Find correct function --- .../Writers/TypeScript/CodeFunctionWriter.cs | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index fec074330c..85e63d2334 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -59,13 +59,6 @@ private string GetSerializationMethodsForPrimitiveUnionTypes(CodeComposedTypeBas return string.Join(" ?? ", composedType.Types.Where(x => IsPrimitiveType(x, composedType)).Select(x => $"{parseNodeParameterName}{optionalChainingSymbol}." + conventions.GetDeserializationMethodName(x, codeElement.OriginalLocalMethod))); } - private void WriteFactoryMethodBodyForPrimitives(CodeComposedTypeBase composedType, CodeFunction codeElement, LanguageWriter writer, CodeParameter? parseNodeParameter) - { - ArgumentNullException.ThrowIfNull(parseNodeParameter); - string primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, parseNodeParameter.Name.ToFirstCharacterLowerCase(), codeElement); - writer.WriteLine($"return {primitiveValuesUnionString};"); - } - private static CodeParameter? GetComposedTypeParameter(CodeFunction codeElement) { return codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(x => GetOriginalComposedType(x) is not null); @@ -238,38 +231,27 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsPrimitiveType): - WriteFactoryMethodBodyForPrimitives(type, codeElement, writer, parseNodeParameter); + case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsPrimitiveType): + string primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, parseNodeParameter!.Name.ToFirstCharacterLowerCase(), codeElement); + writer.WriteLine($"return {primitiveValuesUnionString};"); break; case CodeUnionType _ when parseNodeParameter != null: - WriteFactoryMethodBodyForCodeUnionType(codeElement, returnType, writer, parseNodeParameter); + WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); + // The default discriminator is useful when the discriminator information is not provided. + WriteDefaultDiscriminator(codeElement, returnType, writer); break; case CodeIntersectionType _ when parseNodeParameter != null: WriteDefaultDiscriminator(codeElement, returnType, writer); break; default: - WriteNormalFactoryMethodBody(codeElement, returnType, writer); + if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType && parseNodeParameter != null) + WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); + + WriteDefaultDiscriminator(codeElement, returnType, writer); break; } } - private void WriteFactoryMethodBodyForCodeUnionType(CodeFunction codeElement, string returnType, LanguageWriter writer, CodeParameter parseNodeParameter) - { - WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); - // The default discriminator is useful when the discriminator information is not provided. - WriteDefaultDiscriminator(codeElement, returnType, writer); - } - - private void WriteNormalFactoryMethodBody(CodeFunction codeElement, string returnType, LanguageWriter writer) - { - var parseNodeParameter = codeElement.OriginalLocalMethod.Parameters.OfKind(CodeParameterKind.ParseNode); - if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType && parseNodeParameter != null) - { - WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); - } - WriteDefaultDiscriminator(codeElement, returnType, writer); - } - private void WriteDefaultDiscriminator(CodeFunction codeElement, string returnType, LanguageWriter writer) { var nameSpace = codeElement.GetImmediateParentOfType(); @@ -341,10 +323,14 @@ private string GetSerializerFunctionName(CodeFunction currentFunction, CodeType private string FindFunctionInNameSpace(string functionName, CodeFunction currentFunction, CodeType returnType) { var myNamespace = returnType.TypeDefinition!.GetImmediateParentOfType(); + + CodeFunction[] codeFunctions = myNamespace.FindChildrenByName(functionName).ToArray(); + + var codeFunction = codeFunctions + .FirstOrDefault(func => func.GetImmediateParentOfType()?.Name == myNamespace.Name); - CodeFunction? codeFunction = myNamespace.FindChildByName(functionName); if (codeFunction == null) - throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace?.Name}"); + throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace.Name}"); return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, currentFunction, false); } From af4a9349f124283c21fbacc80693ca7287dfd9e9 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:00:26 +0300 Subject: [PATCH 106/117] fix: formatting --- .../Writers/TypeScript/CodeFunctionWriter.cs | 20 +++++++++---------- .../TypeScript/TypeScriptConventionService.cs | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 85e63d2334..99e926ea0e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -231,7 +231,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, switch (composedType) { - case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsPrimitiveType): + case CodeComposedTypeBase type when type.IsComposedOfPrimitives(IsPrimitiveType): string primitiveValuesUnionString = GetSerializationMethodsForPrimitiveUnionTypes(composedType, parseNodeParameter!.Name.ToFirstCharacterLowerCase(), codeElement); writer.WriteLine($"return {primitiveValuesUnionString};"); break; @@ -246,7 +246,7 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, default: if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType && parseNodeParameter != null) WriteDiscriminatorInformation(codeElement, parseNodeParameter, writer); - + WriteDefaultDiscriminator(codeElement, returnType, writer); break; } @@ -314,25 +314,25 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM return codeFunction; } - private string GetDeserializerFunctionName(CodeFunction currentFunction, CodeType returnType) => - FindFunctionInNameSpace($"deserializeInto{returnType.Name.ToFirstCharacterUpperCase()}", currentFunction, returnType); + private string GetDeserializerFunctionName(CodeElement codeElement, CodeType returnType) => + FindFunctionInNameSpace($"deserializeInto{returnType.Name.ToFirstCharacterUpperCase()}", codeElement, returnType); - private string GetSerializerFunctionName(CodeFunction currentFunction, CodeType returnType) => - FindFunctionInNameSpace($"serialize{returnType.Name.ToFirstCharacterUpperCase()}", currentFunction, returnType); + private string GetSerializerFunctionName(CodeElement codeElement, CodeType returnType) => + FindFunctionInNameSpace($"serialize{returnType.Name.ToFirstCharacterUpperCase()}", codeElement, returnType); - private string FindFunctionInNameSpace(string functionName, CodeFunction currentFunction, CodeType returnType) + private string FindFunctionInNameSpace(string functionName, CodeElement codeElement, CodeType returnType) { var myNamespace = returnType.TypeDefinition!.GetImmediateParentOfType(); - + CodeFunction[] codeFunctions = myNamespace.FindChildrenByName(functionName).ToArray(); var codeFunction = codeFunctions - .FirstOrDefault(func => func.GetImmediateParentOfType()?.Name == myNamespace.Name); + .FirstOrDefault(func => func.GetImmediateParentOfType().Name == myNamespace.Name); if (codeFunction == null) throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace.Name}"); - return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, currentFunction, false); + return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); } private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 092827c6e8..dd5f95b356 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -154,12 +154,12 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ */ private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix, bool includeCollectionInformation = true) { - if (!composedType.Types.Any()) - throw new InvalidOperationException($"Composed type should be comprised of at least one type"); - - var typesDelimiter = composedType is CodeUnionType or CodeIntersectionType ? " | " : + if (!composedType.Types.Any()) + throw new InvalidOperationException($"Composed type should be comprised of at least one type"); + + var typesDelimiter = composedType is CodeUnionType or CodeIntersectionType ? " | " : throw new InvalidOperationException("Unknown composed type"); - + var returnTypeString = string.Join(typesDelimiter, composedType.Types.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation: includeCollectionInformation))); return collectionSuffix.Length > 0 ? $"({returnTypeString}){collectionSuffix}" : returnTypeString; } From c5a4ce1cdaad98119318f51063b99f27db7c7b78 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:53:47 +0300 Subject: [PATCH 107/117] fix: unit tests --- it/config.json | 4 ++++ .../UnionOfPrimitiveAndObjects.cs | 5 ++++- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 15 ++++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/it/config.json b/it/config.json index d9984f61c4..1aa1119dae 100644 --- a/it/config.json +++ b/it/config.json @@ -234,6 +234,10 @@ }, "apisguru::stripe.com": { "Suppressions": [ + { + "Language": "typescript", + "Rationale": "https://github.com/microsoft/kiota/issues/1812" + }, { "Language": "go", "Rationale": "https://github.com/microsoft/kiota/issues/2834" diff --git a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs index cbdb6d7211..0f1f428583 100644 --- a/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs +++ b/tests/Kiota.Builder.Tests/OpenApiSampleFiles/UnionOfPrimitiveAndObjects.cs @@ -35,8 +35,11 @@ public static class UnionOfPrimitiveAndObjects oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' + - type: array + items: + $ref: '#/components/schemas/Dog' - type: string - description: Error message + description: Error status example: ""An error occurred while processing the request."" - type: integer description: Error code diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index dc3f17d65c..9554ef69ea 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1487,13 +1487,14 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() writer.Write(function); var serializerFunctionStr = tw.ToString(); - Assert.Contains("switch (typeof petGetResponse.data)", serializerFunctionStr); - Assert.Contains("case \"number\":", serializerFunctionStr); - Assert.Contains("writer.writeNumberValue(\"data\", petGetResponse.data);", serializerFunctionStr); - Assert.Contains("case \"string\":", serializerFunctionStr); - Assert.Contains("writer.writeStringValue(\"data\", petGetResponse.data);", serializerFunctionStr); + + Assert.Contains("case typeof petGetResponse.data === \"number\":", serializerFunctionStr); + Assert.Contains("writer.writeNumberValue(\"data\", petGetResponse.data as number);", serializerFunctionStr); + Assert.Contains("case typeof petGetResponse.data === \"string\":", serializerFunctionStr); + Assert.Contains("writer.writeStringValue(\"data\", petGetResponse.data as string);", serializerFunctionStr); Assert.Contains("default:", serializerFunctionStr); - Assert.Contains("writer.writeObjectValue(\"data\", petGetResponse.data, serializePetGetResponse_data);", serializerFunctionStr); + Assert.Contains("writer.writeObjectValue(\"data\", petGetResponse.data as Cat | Dog | undefined | null, serializePetGetResponse_data);", serializerFunctionStr); + Assert.Contains("writer.writeCollectionOfObjectValues(\"data\", petGetResponse.data as Dog[] | undefined | null, serializePetGetResponse_data);", serializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } @@ -1531,7 +1532,7 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Deserializer() Assert.True(function is not null); writer.Write(function); var deserializerFunctionStr = tw.ToString(); - Assert.Contains("\"data\": n => { petGetResponse.data = n.getNumberValue() ?? n.getStringValue() ?? n.getObjectValue(createPetGetResponse_dataFromDiscriminatorValue); }", deserializerFunctionStr); + Assert.Contains("\"data\": n => { petGetResponse.data = n.getObjectValue(createCatFromDiscriminatorValue) ?? n.getCollectionOfObjectValues(createDogFromDiscriminatorValue) ?? n.getObjectValue(createDogFromDiscriminatorValue) ?? n.getNumberValue() ?? n.getStringValue(); }", deserializerFunctionStr); AssertExtensions.CurlyBracesAreClosed(deserializerFunctionStr, 1); } } From 3c8c85b8ae83f4a5f180aba14bc8ab808b0f11bf Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:52:05 +0300 Subject: [PATCH 108/117] fix: tests --- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 9554ef69ea..4aea843ded 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1443,14 +1443,15 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Factory() Assert.NotNull(modelCodeFile); // Test Factory function - var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory); - Assert.True(function is not null); - writer.Write(function); + var functions = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).ToArray(); + + foreach (var func in functions) + writer.Write(func); + var factoryFunctionStr = tw.ToString(); - Assert.Contains("@returns {Cat | Dog | number | string}", factoryFunctionStr); + Assert.Contains("@returns {Cat | Dog[] | Dog | number | string}", factoryFunctionStr); Assert.Contains("createPetGetResponse_dataFromDiscriminatorValue", factoryFunctionStr); Assert.Contains("deserializeIntoPetGetResponse_data", factoryFunctionStr); - AssertExtensions.CurlyBracesAreClosed(factoryFunctionStr, 1); } [Fact] From 1d69c3cf561f8816703acea63e1d66b3ef96bc0f Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:17:05 +0300 Subject: [PATCH 109/117] format code --- .../Writers/TypeScript/CodeFunctionWriterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 4aea843ded..403804f27c 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1447,7 +1447,7 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Factory() foreach (var func in functions) writer.Write(func); - + var factoryFunctionStr = tw.ToString(); Assert.Contains("@returns {Cat | Dog[] | Dog | number | string}", factoryFunctionStr); Assert.Contains("createPetGetResponse_dataFromDiscriminatorValue", factoryFunctionStr); From fc5bbb97e49f25857bdaa8036af5f9e1f4d059fc Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:24:12 +0300 Subject: [PATCH 110/117] rafactor: reduce code complexity --- src/Kiota.Builder/CodeDOM/CodeElement.cs | 16 ++++--------- src/Kiota.Builder/KiotaBuilder.cs | 4 ++-- .../Refiners/TypeScriptRefiner.cs | 24 +++++++------------ .../TypeScript/TypeScriptConventionService.cs | 16 ++++++------- 4 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeElement.cs b/src/Kiota.Builder/CodeDOM/CodeElement.cs index 50d23303e4..76230b5670 100644 --- a/src/Kiota.Builder/CodeDOM/CodeElement.cs +++ b/src/Kiota.Builder/CodeDOM/CodeElement.cs @@ -33,25 +33,17 @@ protected void EnsureElementsAreChildren(params ICodeElement?[] elements) foreach (var element in elements.Where(x => x != null && (x.Parent == null || x.Parent != this))) element!.Parent = this; } - - public T GetImmediateParentOfType(CodeElement? item = null) => - GetImmediateParentOfTypeOrDefault(item, - e => throw new InvalidOperationException($"item {e.Name} of type {e.GetType()} does not have a parent"))!; - - public T? GetImmediateParentOfTypeOrDefault(CodeElement? item = null, Action? onFail = null) + public T GetImmediateParentOfType(CodeElement? item = null) { if (item == null) - return GetImmediateParentOfTypeOrDefault(this); + return GetImmediateParentOfType(this); if (item is T p) return p; if (item.Parent == null) - { - onFail?.Invoke(item); - return default; - } + throw new InvalidOperationException($"item {item.Name} of type {item.GetType()} does not have a parent"); if (item.Parent is T p2) return p2; - return GetImmediateParentOfTypeOrDefault(item.Parent); + return GetImmediateParentOfType(item.Parent); } public bool IsChildOf(CodeElement codeElement, bool immediateOnly = false) { diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index fc0ddcb44e..eff10da918 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1221,12 +1221,12 @@ private void AddErrorMappingToExecutorMethod(OpenApiUrlTreeNode currentNode, Ope var obsoleteFactoryMethod = (CodeMethod)originalFactoryMethod.Clone(); obsoleteFactoryMethod.ReturnType = new CodeType { Name = obsoleteTypeName, TypeDefinition = obsoleteClassDefinition }; obsoleteClassDefinition.AddMethod(obsoleteFactoryMethod); - var originalSerializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Serializer); + /*var originalSerializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Serializer); var obsoleteSerializerMethod = (CodeMethod)originalSerializerMethod.Clone(); obsoleteClassDefinition.AddMethod(obsoleteSerializerMethod); var originalDeserializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Deserializer); var obsoleteDeserializerMethod = (CodeMethod)originalDeserializerMethod.Clone(); - obsoleteClassDefinition.AddMethod(obsoleteDeserializerMethod); + obsoleteClassDefinition.AddMethod(obsoleteDeserializerMethod);*/ obsoleteClassDefinition.StartBlock.Inherits = (CodeType)codeType.Clone(); var obsoleteClass = codeClass.Parent switch { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 99ea262797..e8cd56c617 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -188,18 +188,15 @@ private static void CorrectSerializerParameters(CodeElement currentElement) if (currentElement is CodeFunction currentFunction && currentFunction.OriginalLocalMethod.Kind is CodeMethodKind.Serializer) { - - foreach (var parameter in currentFunction.OriginalLocalMethod.Parameters) + foreach (var parameter in currentFunction.OriginalLocalMethod.Parameters + .Where(p => GetOriginalComposedType(p.Type) is CodeComposedTypeBase composedType && + composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) { - if (GetOriginalComposedType(parameter.Type) is CodeComposedTypeBase composedType && - composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType)) - { - var newType = (composedType.Clone() as CodeComposedTypeBase)!; - var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); - newType.SetTypes(nonPrimitiveTypes); - parameter.Type = newType; - } - + var composedType = (CodeComposedTypeBase)GetOriginalComposedType(parameter.Type)!; + var newType = (CodeComposedTypeBase)composedType.Clone(); + var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); + newType.SetTypes(nonPrimitiveTypes); + parameter.Type = newType; } } @@ -279,13 +276,10 @@ private static void GenerateRequestBuilderCodeFiles(CodeNamespace modelsNamespac if (modelsNamespace.Parent is not CodeNamespace mainNamespace) return; var elementsToConsider = mainNamespace.Namespaces.Except([modelsNamespace]).OfType().Union(mainNamespace.Classes).ToArray(); foreach (var element in elementsToConsider) - { GenerateRequestBuilderCodeFilesForElement(element); - } + foreach (var element in elementsToConsider) - {// in two separate loops to ensure all the constants are added before the usings are added AddDownwardsConstantsImports(element); - } } private static void GenerateRequestBuilderCodeFilesForElement(CodeElement currentElement) { diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index dd5f95b356..0e483a0947 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -155,7 +155,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ private static string GetComposedTypeTypeString(CodeComposedTypeBase composedType, CodeElement targetElement, string collectionSuffix, bool includeCollectionInformation = true) { if (!composedType.Types.Any()) - throw new InvalidOperationException($"Composed type should be comprised of at least one type"); + throw new InvalidOperationException("Composed type should be comprised of at least one type"); var typesDelimiter = composedType is CodeUnionType or CodeIntersectionType ? " | " : throw new InvalidOperationException("Unknown composed type"); @@ -166,14 +166,9 @@ private static string GetComposedTypeTypeString(CodeComposedTypeBase composedTyp private static string GetTypeAlias(CodeType targetType, CodeElement targetElement) { - if (targetElement is CodeFile codeFile) - return GetTypeAlias(targetType, codeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement)); - if (targetElement.GetImmediateParentOfTypeOrDefault() is CodeFile parentCodeFile) - return GetTypeAlias(targetType, parentCodeFile.GetChildElements(true).SelectMany(GetUsingsFromCodeElement)); - if (targetElement.GetImmediateParentOfType() is IBlock parentBlock) - return GetTypeAlias(targetType, parentBlock.Usings); - - return GetTypeAlias(targetType, Array.Empty()); + var block = targetElement.GetImmediateParentOfType(); + var usings = block is CodeFile cf ? cf.GetChildElements(true).SelectMany(GetUsingsFromCodeElement) : block?.Usings ?? Array.Empty(); + return GetTypeAlias(targetType, usings); } private static string GetTypeAlias(CodeType targetType, IEnumerable usings) @@ -222,8 +217,11 @@ public static bool IsPrimitiveType(string typeName) { TYPE_NUMBER or TYPE_LOWERCASE_STRING or + TYPE_STRING or TYPE_BYTE_ARRAY or TYPE_LOWERCASE_BOOLEAN or + TYPE_BOOLEAN or + TYPE_VOID or TYPE_LOWERCASE_VOID => true, _ => false, }; From 5ca3aa9f1c5a40f4a0940e616e7b22b6863265bb Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:46:11 +0300 Subject: [PATCH 111/117] fix: String as value --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 7 +------ .../Writers/TypeScript/TypeScriptConventionService.cs | 5 +---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 99e926ea0e..9d5ee9e61a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -562,7 +562,7 @@ private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParameter param, string primaryErrorMapping, string primaryErrorMappingKey, CodeFunction codeFunction, LanguageWriter writer) { - var suffix = GetSuffix(otherProp, primaryErrorMapping, primaryErrorMappingKey); + var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; var paramName = param.Name.ToFirstCharacterLowerCase(); var propName = otherProp.Name.ToFirstCharacterLowerCase(); @@ -583,11 +583,6 @@ private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParam } } - private string GetSuffix(CodeProperty otherProp, string primaryErrorMapping, string primaryErrorMappingKey) - { - return otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; - } - private bool IsBackingStoreProperty(CodeProperty otherProp) { return otherProp.Kind is CodePropertyKind.BackingStore; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 0e483a0947..a843919379 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -217,11 +217,8 @@ public static bool IsPrimitiveType(string typeName) { TYPE_NUMBER or TYPE_LOWERCASE_STRING or - TYPE_STRING or TYPE_BYTE_ARRAY or TYPE_LOWERCASE_BOOLEAN or - TYPE_BOOLEAN or - TYPE_VOID or TYPE_LOWERCASE_VOID => true, _ => false, }; @@ -327,7 +324,7 @@ private static string GetDeserializationMethodNameForPrimitiveOrObject(CodeTypeB { return propertyTypeName switch { - TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_BOOLEAN or TYPE_NUMBER or TYPE_GUID or TYPE_DATE or TYPE_DATE_ONLY or TYPE_TIME_ONLY or TYPE_DURATION => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", + TYPE_LOWERCASE_STRING or TYPE_STRING or TYPE_LOWERCASE_BOOLEAN or TYPE_BOOLEAN or TYPE_NUMBER or TYPE_GUID or TYPE_DATE or TYPE_DATE_ONLY or TYPE_TIME_ONLY or TYPE_DURATION => $"get{propertyTypeName.ToFirstCharacterUpperCase()}Value()", _ => $"getObjectValue<{propertyTypeName.ToFirstCharacterUpperCase()}>({GetFactoryMethodName(propType, method)})" }; } From 08469bf63d0b89f45b897b9ac104be8a82b08384 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:01:46 +0300 Subject: [PATCH 112/117] feat: add unit tests --- src/Kiota.Builder/KiotaBuilder.cs | 6 - .../TypeScript/TypeScriptConventionService.cs | 3 +- .../TypeScript/CodeFunctionWriterTests.cs | 190 ++++++++---------- 3 files changed, 86 insertions(+), 113 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 70b9267730..79bce3666e 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -1223,12 +1223,6 @@ private void AddErrorMappingToExecutorMethod(OpenApiUrlTreeNode currentNode, Ope var obsoleteFactoryMethod = (CodeMethod)originalFactoryMethod.Clone(); obsoleteFactoryMethod.ReturnType = new CodeType { Name = obsoleteTypeName, TypeDefinition = obsoleteClassDefinition }; obsoleteClassDefinition.AddMethod(obsoleteFactoryMethod); - /*var originalSerializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Serializer); - var obsoleteSerializerMethod = (CodeMethod)originalSerializerMethod.Clone(); - obsoleteClassDefinition.AddMethod(obsoleteSerializerMethod); - var originalDeserializerMethod = codeClass.Methods.First(static x => x.Kind is CodeMethodKind.Deserializer); - var obsoleteDeserializerMethod = (CodeMethod)originalDeserializerMethod.Clone(); - obsoleteClassDefinition.AddMethod(obsoleteDeserializerMethod);*/ obsoleteClassDefinition.StartBlock.Inherits = (CodeType)codeType.Clone(); var obsoleteClass = codeClass.Parent switch { diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index a843919379..e8984ea218 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -14,6 +14,7 @@ public class TypeScriptConventionService : CommonLanguageConventionService #pragma warning disable CA1707 // Remove the underscores public const string TYPE_INTEGER = "integer"; public const string TYPE_INT64 = "int64"; + public const string TYPE_INT = "int"; public const string TYPE_FLOAT = "float"; public const string TYPE_DOUBLE = "double"; public const string TYPE_BYTE = "byte"; @@ -190,7 +191,7 @@ public static string TranslateTypescriptType(CodeTypeBase type) { return type?.Name switch { - TYPE_INTEGER or TYPE_INT64 or TYPE_FLOAT or TYPE_DOUBLE or TYPE_BYTE or TYPE_SBYTE or TYPE_DECIMAL => TYPE_NUMBER, + TYPE_INTEGER or TYPE_INT or TYPE_INT64 or TYPE_FLOAT or TYPE_DOUBLE or TYPE_BYTE or TYPE_SBYTE or TYPE_DECIMAL => TYPE_NUMBER, TYPE_BINARY or TYPE_BASE64 or TYPE_BASE64URL => TYPE_STRING, TYPE_GUID => TYPE_GUID, TYPE_STRING or TYPE_OBJECT or TYPE_BOOLEAN or TYPE_VOID or TYPE_LOWERCASE_STRING or TYPE_LOWERCASE_OBJECT or TYPE_LOWERCASE_BOOLEAN or TYPE_LOWERCASE_VOID => type.Name.ToFirstCharacterLowerCase(), diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 403804f27c..d16ef6fd8c 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1414,127 +1414,105 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); } - [Fact] - public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Factory() - { - var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; - var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); - var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); - await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocumentAsync(fs); - var node = builder.CreateUriSpace(document); - builder.SetApiRootUrl(); - var codeModel = builder.CreateSourceModel(node); - var rootNS = codeModel.FindNamespaceByName("ApiSdk"); - Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Pet", false); - Assert.NotNull(clientBuilder); - var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); - Assert.NotNull(constructor); - Assert.Empty(constructor.SerializerModules); - Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); - Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); - Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); - Assert.NotNull(modelCodeFile); - - // Test Factory function - var functions = modelCodeFile.GetChildElements().Where(x => x is CodeFunction function && function.OriginalLocalMethod.Kind == CodeMethodKind.Factory).ToArray(); - - foreach (var func in functions) - writer.Write(func); - - var factoryFunctionStr = tw.ToString(); - Assert.Contains("@returns {Cat | Dog[] | Dog | number | string}", factoryFunctionStr); - Assert.Contains("createPetGetResponse_dataFromDiscriminatorValue", factoryFunctionStr); - Assert.Contains("deserializeIntoPetGetResponse_data", factoryFunctionStr); - } - [Fact] public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; - var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); - var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); - await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocumentAsync(fs); - var node = builder.CreateUriSpace(document); - builder.SetApiRootUrl(); - var codeModel = builder.CreateSourceModel(node); - var rootNS = codeModel.FindNamespaceByName("ApiSdk"); - Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Pet", false); - Assert.NotNull(clientBuilder); - var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); - Assert.NotNull(constructor); - Assert.Empty(constructor.SerializerModules); - Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); - Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); - Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); - Assert.NotNull(modelCodeFile); + var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsAsync = false; - // Test Serializer function - var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.Name.EqualsIgnoreCase("serializePetGetResponse")); - Assert.True(function is not null); - writer.Write(function); - var serializerFunctionStr = tw.ToString(); + var modelNameSpace = root.AddNamespace($"{root.Name}.models"); + var composedType = new CodeUnionType { Name = "Union" }; + composedType.AddType(new CodeType { Name = "string" }, new CodeType { Name = "int" }, + new CodeType + { + Name = "ArrayOfObjects", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + TypeDefinition = TestHelper.CreateModelClass(modelNameSpace, "ArrayOfObjects") + }, + new CodeType + { + Name = "SingleObject", + TypeDefinition = TestHelper.CreateModelClass(modelNameSpace, "SingleObject") + }); + parentClass.AddProperty(new CodeProperty + { + Name = "property", + Type = composedType + }); - Assert.Contains("case typeof petGetResponse.data === \"number\":", serializerFunctionStr); - Assert.Contains("writer.writeNumberValue(\"data\", petGetResponse.data as number);", serializerFunctionStr); - Assert.Contains("case typeof petGetResponse.data === \"string\":", serializerFunctionStr); - Assert.Contains("writer.writeStringValue(\"data\", petGetResponse.data as string);", serializerFunctionStr); - Assert.Contains("default:", serializerFunctionStr); - Assert.Contains("writer.writeObjectValue(\"data\", petGetResponse.data as Cat | Dog | undefined | null, serializePetGetResponse_data);", serializerFunctionStr); - Assert.Contains("writer.writeCollectionOfObjectValues(\"data\", petGetResponse.data as Dog[] | undefined | null, serializePetGetResponse_data);", serializerFunctionStr); + TestHelper.AddSerializationPropertiesToModelClass(parentClass); + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var serializeFunction = root.FindChildByName($"Serialize{parentClass.Name.ToFirstCharacterUpperCase()}"); + Assert.NotNull(serializeFunction); + var parentNS = serializeFunction.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", serializeFunction); + writer.Write(serializeFunction); + var result = tw.ToString(); - AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1); + Assert.Contains("case typeof parentClass.property === \"string\"", result); + Assert.Contains("writer.writeStringValue(\"property\", parentClass.property as string);", result); + Assert.Contains("case typeof parentClass.property === \"number\"", result); + Assert.Contains("writer.writeNumberValue(\"property\", parentClass.property as number);", result); + Assert.Contains( + "writer.writeCollectionOfObjectValues(\"property\", parentClass.property as ArrayOfObjects[] | undefined | null", + result); + Assert.Contains( + "writer.writeObjectValue(\"property\", parentClass.property as SingleObject | undefined | null", + result); + Assert.Contains("writeStringValue", result); + Assert.Contains("writeCollectionOfPrimitiveValues", result); + Assert.Contains("writeCollectionOfObjectValues", result); + Assert.Contains("serializeSomeComplexType", result); + Assert.Contains("writeEnumValue", result); + Assert.Contains("writer.writeAdditionalData", result); + Assert.Contains("definedInParent", result, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Deserializer() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; - var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); - await File.WriteAllTextAsync(tempFilePath, UnionOfPrimitiveAndObjects.openApiSpec); - var mockLogger = new Mock>(); - var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Pet", Serializers = ["none"], Deserializers = ["none"] }, _httpClient); - await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocumentAsync(fs); - var node = builder.CreateUriSpace(document); - builder.SetApiRootUrl(); - var codeModel = builder.CreateSourceModel(node); - var rootNS = codeModel.FindNamespaceByName("ApiSdk"); - Assert.NotNull(rootNS); - var clientBuilder = rootNS.FindChildByName("Pet", false); - Assert.NotNull(clientBuilder); - var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); - Assert.NotNull(constructor); - Assert.Empty(constructor.SerializerModules); - Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); - Assert.NotNull(rootNS); - var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pet"); - Assert.NotNull(modelsNS); - var modelCodeFile = modelsNS.FindChildByName("petRequestBuilder", false); - Assert.NotNull(modelCodeFile); + var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Deserializer; + method.IsAsync = false; - // Test Serializer function - var function = modelCodeFile.GetChildElements().FirstOrDefault(x => x is CodeFunction function && function.Name.EqualsIgnoreCase("deserializeIntoPetGetResponse")); - Assert.True(function is not null); - writer.Write(function); - var deserializerFunctionStr = tw.ToString(); - Assert.Contains("\"data\": n => { petGetResponse.data = n.getObjectValue(createCatFromDiscriminatorValue) ?? n.getCollectionOfObjectValues(createDogFromDiscriminatorValue) ?? n.getObjectValue(createDogFromDiscriminatorValue) ?? n.getNumberValue() ?? n.getStringValue(); }", deserializerFunctionStr); - AssertExtensions.CurlyBracesAreClosed(deserializerFunctionStr, 1); + var modelNameSpace = root.AddNamespace($"{root.Name}.models"); + var composedType = new CodeUnionType { Name = "Union" }; + composedType.AddType(new CodeType { Name = "string" }, new CodeType { Name = "int" }, + new CodeType + { + Name = "ArrayOfObjects", + CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array, + TypeDefinition = TestHelper.CreateModelClass(modelNameSpace, "ArrayOfObjects") + }, + new CodeType + { + Name = "SingleObject", + TypeDefinition = TestHelper.CreateModelClass(modelNameSpace, "SingleObject") + }); + parentClass.AddProperty(new CodeProperty + { + Name = "property", + Type = composedType + }); + + TestHelper.AddSerializationPropertiesToModelClass(parentClass); + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var serializeFunction = root.FindChildByName($"DeserializeInto{parentClass.Name.ToFirstCharacterUpperCase()}"); + Assert.NotNull(serializeFunction); + var parentNS = serializeFunction.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", serializeFunction); + writer.Write(serializeFunction); + var result = tw.ToString(); + + Assert.Contains("\"property\": n => { parentClass.property = n.getCollectionOfObjectValues(createArrayOfObjectsFromDiscriminatorValue) ?? n.getNumberValue() ?? n.getObjectValue(createSingleObjectFromDiscriminatorValue) ?? n.getStringValue(); }", result); } } From 6ef11d5bab21f6765abe6c5e085232a2fbd1ffb7 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:18:41 +0300 Subject: [PATCH 113/117] code cleanup --- it/config.json | 2 +- src/Kiota.Builder/CodeDOM/CodeFile.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 74 +++++++++---------- .../TypeScriptConventionServiceTests.cs | 2 +- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/it/config.json b/it/config.json index 1aa1119dae..588285507a 100644 --- a/it/config.json +++ b/it/config.json @@ -236,7 +236,7 @@ "Suppressions": [ { "Language": "typescript", - "Rationale": "https://github.com/microsoft/kiota/issues/1812" + "Rationale": "https://github.com/microsoft/kiota/issues/5256" }, { "Language": "go", diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 41f09351db..6652799d6e 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -25,7 +25,7 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement .SelectMany(static x => x.GetChildElements(false)) .OfType() .SelectMany(static x => x.Usings) - .Union(GetChildElements(true).Where(x => x is CodeConstant).Cast() + .Union(GetChildElements(true).Where(static x => x is CodeConstant).Cast() .SelectMany(static x => x.StartBlock.Usings)); } public class CodeFileDeclaration : ProprietableBlockDeclaration diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index e8cd56c617..e5267cea1e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -192,7 +192,7 @@ private static void CorrectSerializerParameters(CodeElement currentElement) .Where(p => GetOriginalComposedType(p.Type) is CodeComposedTypeBase composedType && composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) { - var composedType = (CodeComposedTypeBase)GetOriginalComposedType(parameter.Type)!; + var composedType = GetOriginalComposedType(parameter.Type)!; var newType = (CodeComposedTypeBase)composedType.Clone(); var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); newType.SetTypes(nonPrimitiveTypes); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 9d5ee9e61a..33812bb63f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -300,7 +300,7 @@ private string GetFunctionName(CodeElement codeElement, string returnType, CodeM return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false); } - private CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) + private static CodeFunction? FindCodeFunctionInParentNamespaces(string functionName, CodeNamespace? parentNamespace) { CodeFunction? codeFunction = null; @@ -377,15 +377,12 @@ private static bool IsCollectionOfEnum(CodeProperty property) private void WritePropertySerializer(string modelParamName, CodeProperty codeProperty, LanguageWriter writer, CodeFunction codeFunction) { - var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); - var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var propTypeName = GetTypescriptTypeString(codeProperty.Type, codeProperty.Parent!, false, inlineComposedTypeString: true); var serializationName = GetSerializationMethodName(codeProperty.Type, codeFunction.OriginalLocalMethod); var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; - var composedType = GetOriginalComposedType(codeProperty.Type); if (customSerializationWriters.Contains(serializationName) && codeProperty.Type is CodeType propType && propType.TypeDefinition is not null) { @@ -397,24 +394,39 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro } else { - if (!string.IsNullOrWhiteSpace(spreadOperator)) - writer.WriteLine($"if({modelParamName}.{codePropertyName})"); - if (composedType is not null && (composedType.IsComposedOfPrimitives(IsPrimitiveType) || composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) - WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty); - else - writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); + WritePropertySerializationStatement(codeProperty, modelParamName, serializationName, defaultValueSuffix, codeFunction, writer); } } - private void WriteSerializationStatementForComposedTypeProperty(CodeComposedTypeBase composedType, string modelParamName, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string? serializeName) + private void WritePropertySerializationStatement(CodeProperty codeProperty, string modelParamName, string? serializationName, string? defaultValueSuffix, CodeFunction codeFunction, LanguageWriter writer) { var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); + var composedType = GetOriginalComposedType(codeProperty.Type); - var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; + if (!string.IsNullOrWhiteSpace(spreadOperator)) + writer.WriteLine($"if({modelParamName}.{codePropertyName})"); + if (composedType is not null && (composedType.IsComposedOfPrimitives(IsPrimitiveType) || composedType.IsComposedOfObjectsAndPrimitives(IsPrimitiveType))) + WriteSerializationStatementForComposedTypeProperty(composedType, modelParamName, codeFunction, writer, codeProperty, string.Empty); + else + writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); + } + private void WriteSerializationStatementForComposedTypeProperty(CodeComposedTypeBase composedType, string modelParamName, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string? serializeName) + { + var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty; writer.StartBlock("switch (true) {"); + WriteComposedTypeSwitchClause(composedType, method, writer, codeProperty, modelParamName, defaultValueSuffix); + WriteComposedTypeDefaultClause(composedType, writer, codeProperty, modelParamName, defaultValueSuffix, serializeName); + writer.CloseBlock(); + } + + private void WriteComposedTypeSwitchClause(CodeComposedTypeBase composedType, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix) + { + var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); + var isCollectionOfEnum = IsCollectionOfEnum(codeProperty); + var spreadOperator = isCollectionOfEnum ? "..." : string.Empty; foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType))) { @@ -429,7 +441,11 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix} as {nodeType});"); writer.CloseBlock("break;"); } + } + private void WriteComposedTypeDefaultClause(CodeComposedTypeBase composedType, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix, string? serializeName) + { + var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); if (nonPrimitiveTypes.Length > 0) { @@ -448,10 +464,9 @@ private void WriteSerializationStatementForComposedTypeProperty(CodeComposedType } writer.CloseBlock("break;"); } - - writer.CloseBlock(); } + private string GetSerializationMethodName(CodeTypeBase propertyType, CodeMethod method) { ArgumentNullException.ThrowIfNull(propertyType); @@ -526,7 +541,10 @@ private void WriteDeserializerFunctionProperties(CodeParameter param, CodeInterf var properties = codeInterface.Properties.Where(static x => x.IsOfKind(CodePropertyKind.Custom, CodePropertyKind.BackingStore) && !x.ExistsInBaseType); writer.StartBlock("return {"); - WriteInheritsBlock(codeInterface, param, writer); + if (codeInterface.StartBlock.Implements.FirstOrDefault(static x => x.TypeDefinition is CodeInterface) is CodeType type && type.TypeDefinition is CodeInterface inherits) + { + writer.WriteLine($"...deserializeInto{inherits.Name.ToFirstCharacterUpperCase()}({param.Name.ToFirstCharacterLowerCase()}),"); + } var (primaryErrorMapping, primaryErrorMappingKey) = GetPrimaryErrorMapping(codeFunction, param); foreach (var otherProp in properties) @@ -537,15 +555,7 @@ private void WriteDeserializerFunctionProperties(CodeParameter param, CodeInterf writer.CloseBlock(); } - private void WriteInheritsBlock(CodeInterface codeInterface, CodeParameter param, LanguageWriter writer) - { - if (codeInterface.StartBlock.Implements.FirstOrDefault(static x => x.TypeDefinition is CodeInterface) is CodeType type && type.TypeDefinition is CodeInterface inherits) - { - writer.WriteLine($"...deserializeInto{inherits.Name.ToFirstCharacterUpperCase()}({param.Name.ToFirstCharacterLowerCase()}),"); - } - } - - private (string, string) GetPrimaryErrorMapping(CodeFunction codeFunction, CodeParameter param) + private static (string, string) GetPrimaryErrorMapping(CodeFunction codeFunction, CodeParameter param) { var primaryErrorMapping = string.Empty; var primaryErrorMappingKey = string.Empty; @@ -566,9 +576,9 @@ private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParam var paramName = param.Name.ToFirstCharacterLowerCase(); var propName = otherProp.Name.ToFirstCharacterLowerCase(); - if (IsBackingStoreProperty(otherProp)) + if (otherProp.Kind is CodePropertyKind.BackingStore) { - WriteBackingStoreProperty(writer, paramName, propName, suffix); + writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); } else if (GetOriginalComposedType(otherProp.Type) is { } composedType) { @@ -583,17 +593,7 @@ private void WritePropertyDeserializationBlock(CodeProperty otherProp, CodeParam } } - private bool IsBackingStoreProperty(CodeProperty otherProp) - { - return otherProp.Kind is CodePropertyKind.BackingStore; - } - - private void WriteBackingStoreProperty(LanguageWriter writer, string paramName, string propName, string suffix) - { - writer.WriteLine($"\"{BackingStoreEnabledKey}\": n => {{ {paramName}.{propName} = true;{suffix} }},"); - } - - private string GetDefaultValueSuffix(CodeProperty otherProp) + private static string GetDefaultValueSuffix(CodeProperty otherProp) { var defaultValue = GetDefaultValueLiteralForProperty(otherProp); return !string.IsNullOrEmpty(defaultValue) && !defaultValue.EqualsIgnoreCase("\"null\"") ? $" ?? {defaultValue}" : string.Empty; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs index 0adf934337..3a311766b8 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptConventionServiceTests.cs @@ -23,7 +23,7 @@ public void TranslateType_ReturnsCorrectTranslation_WhenComposedTypeIsNotNull() Assert.Equal("Test", result); } - public CodeType CurrentType() + private static CodeType CurrentType() { CodeType currentType = new CodeType { Name = "SomeType" }; var root = CodeNamespace.InitRootNamespace(); From 527c700d603338534b2d9acaf4c750add6e584cf Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:32:30 +0300 Subject: [PATCH 114/117] sonar cleanup --- src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 33812bb63f..5a80b74ad8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -326,8 +326,8 @@ private string FindFunctionInNameSpace(string functionName, CodeElement codeElem CodeFunction[] codeFunctions = myNamespace.FindChildrenByName(functionName).ToArray(); - var codeFunction = codeFunctions - .FirstOrDefault(func => func.GetImmediateParentOfType().Name == myNamespace.Name); + var codeFunction = Array.Find(codeFunctions, + func => func.GetImmediateParentOfType().Name == myNamespace.Name); if (codeFunction == null) throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace.Name}"); @@ -443,7 +443,7 @@ private void WriteComposedTypeSwitchClause(CodeComposedTypeBase composedType, Co } } - private void WriteComposedTypeDefaultClause(CodeComposedTypeBase composedType, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix, string? serializeName) + private static void WriteComposedTypeDefaultClause(CodeComposedTypeBase composedType, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix, string? serializeName) { var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase(); var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray(); From 3bb9d6da4ac68206a7348ba1981c4b15ec464d15 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:57:41 +0300 Subject: [PATCH 115/117] fix: compilation errors from main branch --- .../TypeScript/TypeScriptConventionService.cs | 2 +- .../Export/PublicAPIExportServiceTests.cs | 2 +- .../TypeScriptLanguageRefinerTests.cs | 4 +-- .../TypeScript/CodeFunctionWriterTests.cs | 36 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index e8984ea218..a635ef6cd8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -140,7 +140,7 @@ public static string GetTypescriptTypeString(CodeTypeBase code, CodeElement targ ? alias : TranslateTypescriptType(currentType); - var genericParameters = currentType.GenericTypeParameterValues.Count != 0 + var genericParameters = currentType.GenericTypeParameterValues.Any() ? $"<{string.Join(", ", currentType.GenericTypeParameterValues.Select(x => GetTypescriptTypeString(x, targetElement, includeCollectionInformation)))}>" : string.Empty; diff --git a/tests/Kiota.Builder.Tests/Export/PublicAPIExportServiceTests.cs b/tests/Kiota.Builder.Tests/Export/PublicAPIExportServiceTests.cs index a3232b9d73..51b4306f49 100644 --- a/tests/Kiota.Builder.Tests/Export/PublicAPIExportServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Export/PublicAPIExportServiceTests.cs @@ -200,7 +200,7 @@ private static void ValidateExportTypeScript(string[] exportContents) Assert.Contains("exportNamespace.models.microsoft.graph.User~~>AdditionalDataHolder; Parsable", exportContents);// captures implemented interfaces Assert.Contains("exportNamespace.models.microsoft.graph.User::|public|id:string", exportContents);// captures property location,type and access inheritance. No getter/setter in TS // NOTE: No constructors in TS - Assert.Contains("exportNamespace.me.meRequestBuilder::|public|ToGetRequestInformation(requestConfiguration?:RequestConfiguration):RequestInformation", exportContents);// captures methods, their parameters(name and types), return and access + Assert.Contains("exportNamespace.me.meRequestBuilder::|public|toGetRequestInformation(requestConfiguration?:RequestConfiguration):RequestInformation", exportContents);// captures methods, their parameters(name and types), return and access Assert.Contains("exportNamespace.models.microsoft.graph::createUserFromDiscriminatorValue(parseNode:ParseNode):User", exportContents);// captures code functions Assert.Contains("exportNamespace.models.microsoft.graph::deserializeIntoUser(User:User={}):Record void>", exportContents);// captures code functions and default params Assert.Contains("exportNamespace.models.microsoft.graph.importance::0000-low", exportContents);// captures enum members diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 4a531c178b..8cbd26de8b 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -856,7 +856,7 @@ public async Task AddsUsingForUntypedNodeAsync() Assert.Equal("@microsoft/kiota-abstractions", nodeUsing[0].Declaration.Name); } [Fact] - public async Task ParsesAndRefinesUnionOfPrimitiveValues() + public async Task ParsesAndRefinesUnionOfPrimitiveValuesAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -876,7 +876,7 @@ public async Task ParsesAndRefinesUnionOfPrimitiveValues() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 51d7dcd874..b2894a186a 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1131,7 +1131,7 @@ public async Task WritesConstructorWithEnumValueAsync() Assert.Contains($" ?? {codeEnum.CodeEnumObject.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up } [Fact] - public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() + public async Task Writes_UnionOfPrimitiveValues_FactoryFunctionAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1151,7 +1151,7 @@ public async Task Writes_UnionOfPrimitiveValues_FactoryFunction() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); @@ -1178,7 +1178,7 @@ export function createPrimitivesFromDiscriminatorValue(parseNode: ParseNode | un } [Fact] - public async Task Writes_UnionOfObjects_FactoryMethod() + public async Task Writes_UnionOfObjects_FactoryMethodAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1198,7 +1198,7 @@ public async Task Writes_UnionOfObjects_FactoryMethod() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pets"); Assert.NotNull(modelsNS); @@ -1219,7 +1219,7 @@ public async Task Writes_UnionOfObjects_FactoryMethod() } [Fact] - public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() + public async Task Writes_UnionOfPrimitiveValues_SerializerFunctionAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1239,7 +1239,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.primitives"); Assert.NotNull(modelsNS); @@ -1260,7 +1260,7 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunction() } [Fact] - public async Task Writes_UnionOfObjects_SerializerFunctions() + public async Task Writes_UnionOfObjects_SerializerFunctionsAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1280,7 +1280,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.pets"); Assert.NotNull(modelsNS); @@ -1301,7 +1301,7 @@ public async Task Writes_UnionOfObjects_SerializerFunctions() } [Fact] - public async Task Writes_CodeIntersectionType_FactoryMethod() + public async Task Writes_CodeIntersectionType_FactoryMethodAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1321,7 +1321,7 @@ public async Task Writes_CodeIntersectionType_FactoryMethod() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); Assert.NotNull(modelsNS); @@ -1339,7 +1339,7 @@ public async Task Writes_CodeIntersectionType_FactoryMethod() } [Fact] - public async Task Writes_CodeIntersectionType_DeserializerFunctions() + public async Task Writes_CodeIntersectionType_DeserializerFunctionsAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1359,7 +1359,7 @@ public async Task Writes_CodeIntersectionType_DeserializerFunctions() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); Assert.NotNull(modelsNS); @@ -1377,7 +1377,7 @@ public async Task Writes_CodeIntersectionType_DeserializerFunctions() } [Fact] - public async Task Writes_CodeIntersectionType_SerializerFunctions() + public async Task Writes_CodeIntersectionType_SerializerFunctionsAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); @@ -1397,7 +1397,7 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() Assert.NotNull(constructor); Assert.Empty(constructor.SerializerModules); Assert.Empty(constructor.DeserializerModules); - await ILanguageRefiner.Refine(generationConfiguration, rootNS); + await ILanguageRefiner.RefineAsync(generationConfiguration, rootNS); Assert.NotNull(rootNS); var modelsNS = rootNS.FindNamespaceByName("ApiSdk.foobar"); Assert.NotNull(modelsNS); @@ -1415,7 +1415,7 @@ public async Task Writes_CodeIntersectionType_SerializerFunctions() } [Fact] - public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() + public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_SerializerAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); @@ -1445,7 +1445,7 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() TestHelper.AddSerializationPropertiesToModelClass(parentClass); - await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); var serializeFunction = root.FindChildByName($"Serialize{parentClass.Name.ToFirstCharacterUpperCase()}"); Assert.NotNull(serializeFunction); var parentNS = serializeFunction.GetImmediateParentOfType(); @@ -1474,7 +1474,7 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Serializer() } [Fact] - public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Deserializer() + public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_DeserializerAsync() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); @@ -1503,7 +1503,7 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_Deserializer() }); TestHelper.AddSerializationPropertiesToModelClass(parentClass); - await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); var serializeFunction = root.FindChildByName($"DeserializeInto{parentClass.Name.ToFirstCharacterUpperCase()}"); Assert.NotNull(serializeFunction); var parentNS = serializeFunction.GetImmediateParentOfType(); From d4c29c266d6b20e777c788935dced202b769c7de Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 29 Aug 2024 09:47:34 -0400 Subject: [PATCH 116/117] Update src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs --- src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index c7ba592eca..d5114aacf6 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -85,10 +85,7 @@ public DeprecationInformation? Deprecation public bool IsComposedOfObjectsAndPrimitives(Func checkIfPrimitive) { // Count the number of primitives in Types - int primitiveCount = Types.Count(x => checkIfPrimitive(x, this)); - - // If the number of primitives is less than the total count, it means the rest are objects - return primitiveCount > 0 && primitiveCount < Types.Count(); + return Types.Any(x => checkIfPrimitive(x, this)) && Type.Any(x => !checkIfPrimitive(x, this)); } } From bbee168c3bd1a43a68308dc99fa5257b4a3122ff Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 29 Aug 2024 09:50:40 -0400 Subject: [PATCH 117/117] fix: typo --- src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs index d5114aacf6..d2b5241d37 100644 --- a/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs +++ b/src/Kiota.Builder/CodeDOM/CodeComposedTypeBase.cs @@ -85,7 +85,7 @@ public DeprecationInformation? Deprecation public bool IsComposedOfObjectsAndPrimitives(Func checkIfPrimitive) { // Count the number of primitives in Types - return Types.Any(x => checkIfPrimitive(x, this)) && Type.Any(x => !checkIfPrimitive(x, this)); + return Types.Any(x => checkIfPrimitive(x, this)) && Types.Any(x => !checkIfPrimitive(x, this)); } }