From 7009153699d941c7ab63593bcff5e2b11b95e6ab Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 11 Dec 2023 15:24:43 -0500 Subject: [PATCH 01/40] - adds missing documentation and deprecation information in typescript for interfaces Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeInterface.cs | 38 +++++++++++++++++-- .../Refiners/TypeScriptRefiner.cs | 4 ++ .../CodeInterfaceDeclarationWriter.cs | 3 +- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index b4d0c1ec93..f5a6ace32d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -1,19 +1,51 @@ -namespace Kiota.Builder.CodeDOM; +using System; +using System.Linq; + +namespace Kiota.Builder.CodeDOM; public enum CodeInterfaceKind { Custom, Model, QueryParameters, - RequestConfiguration + RequestBuilder, } -public class CodeInterface : ProprietableBlock, ITypeDefinition +public class CodeInterface : ProprietableBlock, ITypeDefinition, IDeprecableElement { public CodeClass? OriginalClass { get; set; } + public DeprecationInformation? Deprecation + { + get; set; + } + + public static CodeInterface FromRequestBuilder(CodeClass codeClass) + { + ArgumentNullException.ThrowIfNull(codeClass); + if (codeClass.Kind != CodeClassKind.RequestBuilder) throw new InvalidOperationException($"Cannot create a request builder interface from a non request builder class"); + var result = new CodeInterface + { + Name = codeClass.Name, + Kind = CodeInterfaceKind.RequestBuilder, + OriginalClass = codeClass, + Documentation = codeClass.Documentation, + Deprecation = codeClass.Deprecation, + }; + + result.AddMethod(codeClass.Methods + .Where(static x => x.Kind is CodeMethodKind.RequestGenerator or + CodeMethodKind.RequestExecutor or + CodeMethodKind.IndexerBackwardCompatibility or + CodeMethodKind.RequestBuilderWithParameters) + .Select(static x => (CodeMethod)x.Clone()).ToArray()); + + result.AddUsing(codeClass.Usings.ToArray()); + + return result; + } } public class InterfaceDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 7515d598d1..c06a4c2a5f 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -569,6 +569,8 @@ codeClass.Parent is CodeClass parentClass && { Name = codeClass.Name, Kind = CodeInterfaceKind.QueryParameters, + Documentation = codeClass.Documentation, + Deprecation = codeClass.Deprecation, }; parentClass.RemoveChildElement(codeClass); var codeInterface = targetNS.AddInterface(insertValue).First(); @@ -899,6 +901,8 @@ private static CodeInterface CreateModelInterface(CodeClass modelClass, Func x.Name).Aggregate(static (x, y) => x + ", " + y)}" : string.Empty; writer.StartBlock($"export interface {codeElement.Name.ToFirstCharacterUpperCase()}{derivation} {{"); } From cfbebab5c7da3d4302495f20fec515e9d1c66a4c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 11 Dec 2023 15:48:47 -0500 Subject: [PATCH 02/40] - draft TS request builders as interfaces Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 63 ++++ src/Kiota.Builder/CodeDOM/CodeInterface.cs | 8 +- .../Refiners/TypeScriptRefiner.cs | 33 ++- .../Writers/TypeScript/CodeConstantWriter.cs | 2 +- .../Writers/TypeScript/CodeMethodWriter.cs | 273 ++++-------------- .../Writers/TypeScript/CodePropertyWriter.cs | 13 +- .../TypeScript/TypeScriptConventionService.cs | 12 - .../TypeScript/CodeMethodWriterTests.cs | 19 +- 8 files changed, 158 insertions(+), 265 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index e834b074e3..0acc18bb32 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -11,6 +11,12 @@ public CodeElement? OriginalCodeElement get; set; } +#pragma warning disable CA1056 // URI-like properties should not be strings + public string? UriTemplate + { + get; init; + } +#pragma warning restore CA1056 // URI-like properties should not be strings public static CodeConstant? FromQueryParametersMapping(CodeInterface source) { ArgumentNullException.ThrowIfNull(source); @@ -33,9 +39,66 @@ public CodeElement? OriginalCodeElement OriginalCodeElement = source, }; } + public static CodeConstant? FromRequestBuilderClassToUriTemplate(CodeClass codeClass) + { + ArgumentNullException.ThrowIfNull(codeClass); + if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; + if (codeClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty) throw new InvalidOperationException($"Couldn't find the url template property for class {codeClass.Name}"); + return new CodeConstant + { + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}UriTemplate", + Kind = CodeConstantKind.UriTemplate, + UriTemplate = urlTemplateProperty.DefaultValue, + }; + } + public static CodeConstant? FromRequestBuilderToNavigationMetadata(CodeClass codeClass) + { + ArgumentNullException.ThrowIfNull(codeClass); + if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; + if (!(codeClass.Properties.Any(static x => x.Kind is CodePropertyKind.RequestBuilder) || + codeClass.Methods.Any(x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters))) + return default; + return new CodeConstant + { + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}NavigationMetadata", + Kind = CodeConstantKind.NavigationMetadata, + OriginalCodeElement = codeClass, + }; + } + public static CodeConstant? FromRequestBuilderToRequestsMetadata(CodeClass codeClass) + { + ArgumentNullException.ThrowIfNull(codeClass); + if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; + if (!codeClass.Methods.Any(x => x.Kind is CodeMethodKind.RequestExecutor or CodeMethodKind.RequestGenerator)) + return default; + return new CodeConstant + { + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}RequestsMetadata", + Kind = CodeConstantKind.RequestsMetadata, + OriginalCodeElement = codeClass, + }; + } } public enum CodeConstantKind { + /// + /// Mapper for query parameters from symbol name to serialization name represented as a constant. + /// QueryParametersMapper, + /// + /// Enum mapping keys represented as a constant. + /// EnumObject, + /// + /// Uri template for the request builder. + /// + UriTemplate, + /// + /// Indexer methods, builders with parameters, and request builder properties represented as a single constant for proxy generation. + /// + NavigationMetadata, + /// + /// Request generators and executors represented as a single constant for proxy generation. + /// + RequestsMetadata, } diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index f5a6ace32d..dbcf81d77f 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -35,14 +35,16 @@ public static CodeInterface FromRequestBuilder(CodeClass codeClass) Deprecation = codeClass.Deprecation, }; - result.AddMethod(codeClass.Methods + if (codeClass.Methods .Where(static x => x.Kind is CodeMethodKind.RequestGenerator or CodeMethodKind.RequestExecutor or CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters) - .Select(static x => (CodeMethod)x.Clone()).ToArray()); + .Select(static x => (CodeMethod)x.Clone()).ToArray() is { Length: > 0 } methods) + result.AddMethod(methods); - result.AddUsing(codeClass.Usings.ToArray()); + if (codeClass.Usings.ToArray() is { Length: > 0 } usings) + result.AddUsing(usings); //TODO pass a list of external imports to remove as we create the interface return result; } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index c06a4c2a5f..7126cbaffe 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -222,7 +222,7 @@ private static void GenerateRequestBuilderCodeFiles(CodeNamespace modelsNamespac private static void GenerateRequestBuilderCodeFilesForElement(CodeElement currentElement) { if (currentElement.Parent is CodeNamespace codeNamespace && currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) - GenerateRequestBuilderCodeFile(currentClass, codeNamespace); + GenerateRequestBuilderCodeFile(ReplaceRequestBuilderClassByInterface(currentClass, codeNamespace), codeNamespace); CrawlTree(currentElement, GenerateRequestBuilderCodeFilesForElement); } @@ -242,9 +242,23 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && return null; return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); } - private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) + private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass codeClass, CodeNamespace codeNamespace) { - var executorMethods = codeClass.Methods + if (CodeConstant.FromRequestBuilderToRequestsMetadata(codeClass) is CodeConstant requestsMetadataConstant) + codeNamespace.AddConstant(requestsMetadataConstant); + if (CodeConstant.FromRequestBuilderToNavigationMetadata(codeClass) is CodeConstant navigationConstant) + codeNamespace.AddConstant(navigationConstant); + if (CodeConstant.FromRequestBuilderClassToUriTemplate(codeClass) is CodeConstant uriTemplateConstant) + codeNamespace.AddConstant(uriTemplateConstant); + var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass); + codeNamespace.RemoveChildElement(codeClass); + codeNamespace.AddInterface(interfaceDeclaration); + return interfaceDeclaration; + + } + private static void GenerateRequestBuilderCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) + { + var executorMethods = codeInterface.Methods .Where(static x => x.IsOfKind(CodeMethodKind.RequestExecutor)) .ToArray(); @@ -280,8 +294,17 @@ private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeName .OfType() .ToArray(); + var navigationConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}NavigationMetadata", false); + var requestsMetadataConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}RequestsMetadata", false); + var uriTemplateConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}UriTemplate", false); + + var proxyConstants = new[] { navigationConstant, requestsMetadataConstant, uriTemplateConstant } + .OfType() + .ToArray(); + codeNamespace.RemoveChildElement(inlineRequestAndResponseBodyFiles); - var elements = new CodeElement[] { codeClass } + var elements = new CodeElement[] { codeInterface } + .Union(proxyConstants) .Union(queryParameterInterfaces) .Union(queryParametersMapperConstants) .Union(inlineRequestAndResponseBodyFiles.SelectMany(static x => x.GetChildElements(true))) @@ -290,7 +313,7 @@ private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeName .Distinct() .ToArray(); - codeNamespace.TryAddCodeFile(codeClass.Name, elements); + codeNamespace.TryAddCodeFile(codeInterface.Name, elements); } private static IEnumerable GetUsingsFromCodeElement(CodeElement codeElement) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 70e9b5fee2..3f7d08173e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -12,7 +12,6 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.OriginalCodeElement is null) throw new InvalidOperationException("Original CodeElement cannot be null"); switch (codeElement.Kind) { case CodeConstantKind.QueryParametersMapper: @@ -21,6 +20,7 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w case CodeConstantKind.EnumObject: WriteEnumObjectConstant(codeElement, writer); break; + //TODO new constant types } } private static void WriteQueryParametersMapperConstant(CodeConstant codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 468a172d77..4662e08c36 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -20,67 +20,61 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri if (codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null"); ArgumentNullException.ThrowIfNull(writer); if (codeElement.Parent is CodeFunction) return; - if (codeElement.Parent is not CodeClass parentClass) throw new InvalidOperationException("the parent of a method should be a class"); var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement); var isVoid = "void".EqualsIgnoreCase(returnType); WriteMethodDocumentation(codeElement, writer, isVoid); WriteMethodPrototype(codeElement, writer, returnType, isVoid); - writer.IncreaseIndent(); - var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; - var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); - var requestConfigParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); - var requestParams = new RequestParams(requestBodyParam, requestConfigParam, requestContentType); - WriteDefensiveStatements(codeElement, writer); - switch (codeElement.Kind) + if (codeElement.Parent is CodeClass parentClass) { - case CodeMethodKind.IndexerBackwardCompatibility: - WriteIndexerBody(codeElement, parentClass, returnType, writer); - break; - case CodeMethodKind.Deserializer: - throw new InvalidOperationException("Deserializers are implemented as functions in TypeScript"); - case CodeMethodKind.Serializer: - throw new InvalidOperationException("Serializers are implemented as functions in TypeScript"); - case CodeMethodKind.RequestGenerator: - WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer); - break; - case CodeMethodKind.RequestExecutor: - WriteRequestExecutorBody(codeElement, parentClass, requestParams, isVoid, returnType, writer); - break; - case CodeMethodKind.Getter: - WriteGetterBody(codeElement, writer, parentClass); - break; - case CodeMethodKind.Setter: - WriteSetterBody(codeElement, writer, parentClass); - break; - case CodeMethodKind.RawUrlBuilder: - throw new InvalidOperationException("RawUrlBuilder is implemented in the base type in TypeScript."); - case CodeMethodKind.ClientConstructor: - WriteConstructorBody(parentClass, codeElement, writer, inherits); - WriteApiConstructorBody(parentClass, codeElement, writer); - break; - case CodeMethodKind.Constructor: - WriteConstructorBody(parentClass, codeElement, writer, inherits); - break; - case CodeMethodKind.RequestBuilderWithParameters: - WriteRequestBuilderWithParametersBody(codeElement, parentClass, returnType, writer); - break; - case CodeMethodKind.QueryParametersMapper: - throw new InvalidOperationException("TypeScript relies on a constant for query parameters mapping"); - case CodeMethodKind.Factory: - throw new InvalidOperationException("Factory methods are implemented as functions in TypeScript"); - case CodeMethodKind.RawUrlConstructor: - throw new InvalidOperationException("RawUrlConstructor is not supported as typescript relies on union types."); - case CodeMethodKind.RequestBuilderBackwardCompatibility: - throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); - case CodeMethodKind.ErrorMessageOverride: - throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by the deserializer function in typescript."); - default: - WriteDefaultMethodBody(codeElement, writer); - break; + writer.IncreaseIndent(); + var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; + WriteDefensiveStatements(codeElement, writer); + switch (codeElement.Kind) + { + case CodeMethodKind.IndexerBackwardCompatibility: + throw new InvalidOperationException("IndexerBackwardCompatibility is implemented by constants in TypeScript."); + case CodeMethodKind.Deserializer: + throw new InvalidOperationException("Deserializers are implemented as functions in TypeScript"); + case CodeMethodKind.Serializer: + throw new InvalidOperationException("Serializers are implemented as functions in TypeScript"); + case CodeMethodKind.RequestGenerator: + throw new InvalidOperationException("RequestGenerator is implemented by constants in TypeScript."); + case CodeMethodKind.RequestExecutor: + throw new InvalidOperationException("RequestExecutor is implemented by constants in TypeScript."); + case CodeMethodKind.Getter: + WriteGetterBody(codeElement, writer, parentClass); //TODO double check whether we still need this + break; + case CodeMethodKind.Setter: + WriteSetterBody(codeElement, writer, parentClass); + break; + case CodeMethodKind.RawUrlBuilder: + throw new InvalidOperationException("RawUrlBuilder is implemented in the base type in TypeScript."); + case CodeMethodKind.ClientConstructor: + WriteConstructorBody(parentClass, codeElement, writer, inherits); + WriteApiConstructorBody(parentClass, codeElement, writer); + break; + case CodeMethodKind.Constructor: + WriteConstructorBody(parentClass, codeElement, writer, inherits); + break; + case CodeMethodKind.RequestBuilderWithParameters: + throw new InvalidOperationException("RequestBuilderWithParameters is implemented by constants in TypeScript."); + case CodeMethodKind.QueryParametersMapper: + throw new InvalidOperationException("TypeScript relies on a constant for query parameters mapping"); + case CodeMethodKind.Factory: + throw new InvalidOperationException("Factory methods are implemented as functions in TypeScript"); + case CodeMethodKind.RawUrlConstructor: + throw new InvalidOperationException("RawUrlConstructor is not supported as typescript relies on union types."); + case CodeMethodKind.RequestBuilderBackwardCompatibility: + throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); + case CodeMethodKind.ErrorMessageOverride: + throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by the deserializer function in typescript."); + default: + WriteDefaultMethodBody(codeElement, writer); + break; + } + writer.CloseBlock(); } - writer.CloseBlock(); } internal static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter writer) @@ -98,22 +92,6 @@ internal static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWr writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); } } - private void WriteIndexerBody(CodeMethod codeElement, CodeClass parentClass, string returnType, LanguageWriter writer) - { - if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty && - codeElement.OriginalIndexer != null) - { - conventions.AddParametersAssignment(writer, pathParametersProperty.Type, $"this.{pathParametersProperty.Name}", - parameters: (codeElement.OriginalIndexer.IndexParameter.Type, codeElement.OriginalIndexer.IndexParameter.SerializationName, codeElement.OriginalIndexer.IndexParameter.Name.ToFirstCharacterLowerCase())); - } - conventions.AddRequestBuilderBody(parentClass, returnType, writer, conventions.TempDictionaryVarName); - } - private void WriteRequestBuilderWithParametersBody(CodeMethod codeElement, CodeClass parentClass, string returnType, LanguageWriter writer) - { - var codePathParameters = codeElement.Parameters - .Where(x => x.IsOfKind(CodeParameterKind.Path)); - conventions.AddRequestBuilderBody(parentClass, returnType, writer, pathParameters: codePathParameters); - } private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { var pathParametersProperty = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); @@ -262,122 +240,6 @@ private static void WriteDefaultMethodBody(CodeMethod codeElement, LanguageWrite var promiseSuffix = codeElement.IsAsync ? ")" : string.Empty; writer.WriteLine($"return {promisePrefix}{(codeElement.ReturnType.Name.Equals("string", StringComparison.OrdinalIgnoreCase) ? "''" : "{} as any")}{promiseSuffix};"); } - private void WriteRequestExecutorBody(CodeMethod codeElement, CodeClass parentClass, RequestParams requestParams, bool isVoid, string returnType, LanguageWriter writer) - { - if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - - - var generatorMethodName = parentClass - .Methods - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) - ?.Name - ?.ToFirstCharacterLowerCase(); - writer.WriteLine($"const requestInfo = this.{generatorMethodName}("); - var requestInfoParameters = new CodeParameter?[] { requestParams.requestBody, requestParams.requestContentType, requestParams.requestConfiguration } - .OfType() - .Select(static x => x.Name) - .ToArray(); - if (requestInfoParameters.Length != 0) - { - writer.IncreaseIndent(); - writer.WriteLine(requestInfoParameters.Aggregate(static (x, y) => $"{x}, {y}")); - writer.DecreaseIndent(); - } - writer.WriteLine(");"); - var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); - var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(codeElement, returnType); - var genericTypeForSendMethod = GetSendRequestMethodName(isVoid, isStream, codeElement.ReturnType.IsCollection, returnTypeWithoutCollectionSymbol); - var newFactoryParameter = GetTypeFactory(isVoid, isStream, codeElement, writer); - var errorMappingVarName = "undefined"; - if (codeElement.ErrorMappings.Any()) - { - errorMappingVarName = "errorMapping"; - writer.WriteLine($"const {errorMappingVarName} = {{"); - writer.IncreaseIndent(); - foreach (var errorMapping in codeElement.ErrorMappings) - { - writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {GetFactoryMethodName(errorMapping.Value, codeElement, writer)},"); - } - writer.CloseBlock("} as Record>;"); - } - writer.WriteLine($"return this.requestAdapter.{genericTypeForSendMethod}(requestInfo,{newFactoryParameter} {errorMappingVarName});"); - } - - private string GetTypeFactory(bool isVoid, bool isStream, CodeMethod codeElement, LanguageWriter writer) - { - if (isVoid) return string.Empty; - var typeName = conventions.TranslateType(codeElement.ReturnType); - if (isStream || conventions.IsPrimitiveType(typeName)) return $" \"{typeName}\","; - return $" {GetFactoryMethodName(codeElement.ReturnType, codeElement, writer)},"; - } - private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) - { - if (!codeElement.ReturnType.IsCollection) return fullTypeName; - var clone = (CodeTypeBase)codeElement.ReturnType.Clone(); - clone.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.None; - return conventions.GetTypeString(clone, codeElement); - } - private const string RequestInfoVarName = "requestInfo"; - private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer) - { - if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null"); - if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty) throw new InvalidOperationException("path parameters cannot be null"); - if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty) throw new InvalidOperationException("url template cannot be null"); - - writer.WriteLine($"const {RequestInfoVarName} = new RequestInformation(HttpMethod.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}, {GetPropertyCall(urlTemplateProperty)}, {GetPropertyCall(urlTemplateParamsProperty)});"); - if (requestParams.requestConfiguration != null) - { - var parentBlock = currentClass.Parent switch - { - CodeNamespace codeNamespace => codeNamespace, - CodeFile codeFile => (IBlock)codeFile, - _ => throw new InvalidOperationException("unsupported parent type"), - }; - var queryParametersConstant = parentBlock.FindChildByName($"{currentClass.Name.ToFirstCharacterLowerCase()}{codeElement.HttpMethod.Value.ToString().ToFirstCharacterUpperCase()}QueryParametersMapper"); - var queryParametersConstantName = queryParametersConstant is null ? string.Empty : $", {queryParametersConstant.Name}"; - writer.WriteLine($"{RequestInfoVarName}.configure({requestParams.requestConfiguration.Name}{queryParametersConstantName});"); - } - - if (codeElement.ShouldAddAcceptHeader) - writer.WriteLine($"{RequestInfoVarName}.headers.tryAdd(\"Accept\", \"{codeElement.AcceptHeaderValue}\");"); - if (requestParams.requestBody != null) - { - if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - { - if (requestParams.requestContentType is not null) - writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, {requestParams.requestContentType.Name.ToFirstCharacterLowerCase()});"); - else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) - writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\");"); - } - else if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) - { - ComposeContentInRequestGeneratorBody(requestParams.requestBody, requestAdapterProperty, codeElement.RequestBodyContentType, writer); - } - } - - writer.WriteLine($"return {RequestInfoVarName};"); - } - - private void ComposeContentInRequestGeneratorBody(CodeParameter requestBody, CodeProperty requestAdapterProperty, string contentType, LanguageWriter writer) - { - if (requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - { - writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestBody.Name});"); - return; - } - - var spreadOperator = requestBody.Type.AllTypes.First().IsCollection ? "..." : string.Empty; - if (requestBody.Type is CodeType currentType && (currentType.TypeDefinition is CodeInterface || currentType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) - { - var serializerName = $"serialize{currentType.Name.ToFirstCharacterUpperCase()}"; - writer.WriteLine($"{RequestInfoVarName}.setContentFromParsable(this.{requestAdapterProperty.Name.ToFirstCharacterLowerCase()}, \"{contentType}\", {requestBody.Name}, {serializerName});"); - } - else - { - writer.WriteLine($"{RequestInfoVarName}.setContentFromScalar(this.{requestAdapterProperty.Name.ToFirstCharacterLowerCase()}, \"{contentType}\", {spreadOperator}{requestBody.Name});"); - } - } - private static string GetPropertyCall(CodeProperty property) => $"this.{property.Name}"; private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, bool isVoid) { @@ -402,7 +264,7 @@ private void WriteMethodPrototype(CodeMethod code, LanguageWriter writer, string } internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWriter writer, string returnType, bool isVoid, TypeScriptConventionService pConventions, bool isFunction) { - var accessModifier = isFunction ? string.Empty : pConventions.GetAccessModifier(code.Access); + var accessModifier = isFunction || code.Parent is CodeInterface ? string.Empty : pConventions.GetAccessModifier(code.Access); var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor); var methodName = (code.Kind switch { @@ -425,40 +287,7 @@ internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWrite }; var shouldHaveTypeSuffix = !code.IsAccessor && !isConstructor && !string.IsNullOrEmpty(returnType); var returnTypeSuffix = shouldHaveTypeSuffix ? $" : {asyncReturnTypePrefix}{returnType}{nullableSuffix}{asyncReturnTypeSuffix}" : string.Empty; - writer.WriteLine($"{accessModifier}{functionPrefix}{accessorPrefix}{staticPrefix}{methodName}{asyncPrefix}({parameters}){returnTypeSuffix} {{"); - } - private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod 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, string returnType) - { - if (isVoid) return "sendNoResponseContentAsync"; - if (isCollection) - { - if (conventions.IsPrimitiveType(returnType)) return $"sendCollectionOfPrimitiveAsync<{returnType}>"; - return $"sendCollectionAsync<{returnType}>"; - } - - if (isStream || conventions.IsPrimitiveType(returnType)) return $"sendPrimitiveAsync<{returnType}>"; - return $"sendAsync<{returnType}>"; + var openBracketSuffix = code.Parent is CodeClass || isFunction ? " {" : ";"; + writer.WriteLine($"{accessModifier}{functionPrefix}{accessorPrefix}{staticPrefix}{methodName}{asyncPrefix}({parameters}){returnTypeSuffix}{openBracketSuffix}"); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index f12b8815df..1d42abd261 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -22,8 +22,8 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w case CodeInterface: WriteCodePropertyForInterface(codeElement, writer, returnType, isFlagEnum); break; - case CodeClass codeClass: - WriteCodePropertyForClass(codeElement, codeClass, writer, returnType, isFlagEnum); + case CodeClass: + WriteCodePropertyForClass(codeElement, writer, returnType, isFlagEnum); break; } } @@ -33,17 +33,14 @@ private static void WriteCodePropertyForInterface(CodeProperty codeElement, Lang writer.WriteLine($"{codeElement.Name.ToFirstCharacterLowerCase()}?: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); } - private void WriteCodePropertyForClass(CodeProperty codeElement, CodeClass parentClass, LanguageWriter writer, string returnType, bool isFlagEnum) - { + private void WriteCodePropertyForClass(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) + {//TODO double check we still need this switch (codeElement.Kind) { case CodePropertyKind.ErrorMessageOverride: throw new InvalidOperationException($"Primary message mapping is done in deserializer function in TypeScript."); case CodePropertyKind.RequestBuilder: - writer.StartBlock($"{conventions.GetAccessModifier(codeElement.Access)} get {codeElement.Name.ToFirstCharacterLowerCase()}(): {returnType} {{"); - conventions.AddRequestBuilderBody(parentClass, returnType, writer); - writer.CloseBlock(); - break; + throw new InvalidOperationException($"Request builder property is implemented via a constant in TypeScript."); default: writer.WriteLine($"{conventions.GetAccessModifier(codeElement.Access)} {codeElement.NamePrefix}{codeElement.Name.ToFirstCharacterLowerCase()}{(codeElement.Type.IsNullable ? "?" : string.Empty)}: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); break; diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs index 23785d4e53..886ee7b49f 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptConventionService.cs @@ -28,17 +28,6 @@ internal void WriteAutoGeneratedEnd(LanguageWriter writer) public override string ParseNodeInterfaceName => "ParseNode"; internal string DocCommentStart = "/**"; internal string DocCommentEnd = " */"; -#pragma warning disable CA1822 // Method should be static - internal void AddRequestBuilderBody(CodeClass parentClass, string returnType, LanguageWriter writer, string? urlTemplateVarName = default, IEnumerable? pathParameters = default) - { - var codePathParametersSuffix = !(pathParameters?.Any() ?? false) ? string.Empty : $", {string.Join(", ", pathParameters.Select(x => x.Name.ToFirstCharacterLowerCase()))}"; - if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty && - parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProp) - { - var urlTemplateParams = !string.IsNullOrEmpty(urlTemplateVarName) ? urlTemplateVarName : $"this.{pathParametersProperty.Name}"; - writer.WriteLines($"return new {returnType}({urlTemplateParams}, this.{requestAdapterProp.Name}{codePathParametersSuffix});"); - } - } public override string TempDictionaryVarName => "urlTplParams"; internal void AddParametersAssignment(LanguageWriter writer, CodeTypeBase pathParametersType, string pathParametersReference, string varName = "", params (CodeTypeBase, string, string)[] parameters) { @@ -53,7 +42,6 @@ internal void AddParametersAssignment(LanguageWriter writer, CodeTypeBase pathPa $"{varName}[\"{p.Item2}\"] = {p.Item3}" ).ToArray()); } -#pragma warning restore CA1822 // Method should be static public override string GetAccessModifier(AccessModifier access) { return access switch diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 3787522dae..281865894a 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -171,16 +171,7 @@ public void WritesRequestExecutorBody() method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }); method.AddErrorMapping("403", new CodeType { Name = "Error403", TypeDefinition = error403 }); AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("const requestInfo", result); - Assert.Contains("const errorMapping", result); - Assert.Contains("\"4XX\": createError4XXFromDiscriminatorValue,", result); - Assert.Contains("\"5XX\": createError5XXFromDiscriminatorValue,", result); - Assert.Contains("\"403\": createError403FromDiscriminatorValue,", result); - Assert.Contains("as Record>", result); - Assert.Contains("sendAsync", result); - AssertExtensions.CurlyBracesAreClosed(result); + Assert.Throws(() => writer.Write(method)); } [Fact] public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() @@ -227,7 +218,7 @@ public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() } [Fact] public void DoesntCreateDictionaryOnEmptyErrorMapping() - { + {//TODO move to constants method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; AddRequestBodyParameters(); @@ -239,7 +230,7 @@ public void DoesntCreateDictionaryOnEmptyErrorMapping() } [Fact] public void WritesRequestGeneratorBodyForMultipart() - { + {// TODO move to constants method.Kind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Post; AddRequestProperties(); @@ -252,7 +243,7 @@ public void WritesRequestGeneratorBodyForMultipart() } [Fact] public void WritesRequestExecutorBodyForCollections() - { + {//TODO move to constants method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; @@ -264,7 +255,7 @@ public void WritesRequestExecutorBodyForCollections() } [Fact] public void WritesRequestGeneratorBodyForScalar() - { + {//TODO move to constants method.Kind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Get; AddRequestProperties(); From f5a4438df40ae86a75054af50c434f896f870d0b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 10:41:42 -0500 Subject: [PATCH 03/40] - implements uri template constant - implements request metadata constant Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeInterface.cs | 9 +- .../Refiners/CommonLanguageRefiner.cs | 5 +- .../Refiners/TypeScriptRefiner.cs | 28 ++-- .../Writers/TypeScript/CodeConstantWriter.cs | 125 +++++++++++++++++- .../Writers/TypeScript/CodePropertyWriter.cs | 10 +- 5 files changed, 146 insertions(+), 31 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index dbcf81d77f..f2d077d4bb 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -22,7 +22,7 @@ public DeprecationInformation? Deprecation get; set; } - public static CodeInterface FromRequestBuilder(CodeClass codeClass) + public static CodeInterface FromRequestBuilder(CodeClass codeClass, CodeUsing[]? usingsToAdd = default) { ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) throw new InvalidOperationException($"Cannot create a request builder interface from a non request builder class"); @@ -42,10 +42,15 @@ CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters) .Select(static x => (CodeMethod)x.Clone()).ToArray() is { Length: > 0 } methods) result.AddMethod(methods); + if (codeClass.Properties + .Where(static x => x.Kind is CodePropertyKind.RequestBuilder) + .Select(static x => (CodeProperty)x.Clone()).ToArray() is { Length: > 0 } properties) + result.AddProperty(properties); if (codeClass.Usings.ToArray() is { Length: > 0 } usings) result.AddUsing(usings); //TODO pass a list of external imports to remove as we create the interface - + if (usingsToAdd is { Length: > 0 } usingsToAddList) + result.AddUsing(usingsToAddList); return result; } } diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 8220666d32..9456c4dbb7 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -1379,12 +1379,11 @@ mappingClass.Parent is CodeNamespace mappingNamespace && } CrawlTree(currentElement, RemoveDiscriminatorMappingsTargetingSubNamespaces); } - protected static void MoveRequestBuilderPropertiesToBaseType(CodeElement currentElement, CodeUsing baseTypeUsing, AccessModifier? accessModifier = null, bool addCurrentTypeAsGenericTypeParameter = false) + protected static void MoveRequestBuilderPropertiesToBaseType(CodeElement currentElement, CodeUsing? baseTypeUsing = null, AccessModifier? accessModifier = null, bool addCurrentTypeAsGenericTypeParameter = false) { - ArgumentNullException.ThrowIfNull(baseTypeUsing); if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) { - if (currentClass.StartBlock.Inherits == null) + if (currentClass.StartBlock.Inherits is null && baseTypeUsing is not null) { currentClass.StartBlock.Inherits = new CodeType { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 7126cbaffe..10f2a9a6c9 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -20,16 +20,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlConstructor, CodeMethodKind.RawUrlBuilder); ReplaceReservedNames(generatedCode, new TypeScriptReservedNamesProvider(), static x => $"{x}Escaped"); ReplaceReservedExceptionPropertyNames(generatedCode, new TypeScriptExceptionsReservedNamesProvider(), static x => $"{x}Escaped"); - MoveRequestBuilderPropertiesToBaseType(generatedCode, - new CodeUsing - { - Name = "BaseRequestBuilder", - Declaration = new CodeType - { - Name = AbstractionsPackageName, - IsExternal = true - } - }, addCurrentTypeAsGenericTypeParameter: true); + MoveRequestBuilderPropertiesToBaseType(generatedCode); ReplaceIndexersByMethodsWithParameter(generatedCode, false, static x => $"by{x.ToFirstCharacterUpperCase()}", @@ -250,7 +241,14 @@ private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass cod codeNamespace.AddConstant(navigationConstant); if (CodeConstant.FromRequestBuilderClassToUriTemplate(codeClass) is CodeConstant uriTemplateConstant) codeNamespace.AddConstant(uriTemplateConstant); - var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass); + var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass, [new CodeUsing { + Name = "RequestMetadata", + IsErasable = true, + Declaration = new CodeType { + Name = AbstractionsPackageName, + IsExternal = true, + }, + }]); codeNamespace.RemoveChildElement(codeClass); codeNamespace.AddInterface(interfaceDeclaration); return interfaceDeclaration; @@ -435,18 +433,12 @@ private static bool HasMultipartBody(CodeMethod m) => private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = { new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), AbstractionsPackageName, true, "RequestAdapter"), - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.Options), - AbstractionsPackageName, true, "RequestOption"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), - AbstractionsPackageName, false, "HttpMethod", "RequestInformation"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), - AbstractionsPackageName, true, "RequestOption"), + AbstractionsPackageName, false, "RequestInformation"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), AbstractionsPackageName, true,"SerializationWriter"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Factory), AbstractionsPackageName, true, "ParseNode"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility), - AbstractionsPackageName, false, "getPathParameters"), new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), AbstractionsPackageName, true, "Parsable", "ParsableFactory"), new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model), diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 3f7d08173e..98d40cf2d9 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -2,7 +2,6 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -using Kiota.Builder.Writers.Go; namespace Kiota.Builder.Writers.TypeScript; public class CodeConstantWriter : BaseElementWriter @@ -20,18 +19,130 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w case CodeConstantKind.EnumObject: WriteEnumObjectConstant(codeElement, writer); break; + case CodeConstantKind.UriTemplate: + WriteUriTemplateConstant(codeElement, writer); + break; + case CodeConstantKind.RequestsMetadata: + WriteRequestsMetadataConstant(codeElement, writer); + break; //TODO new constant types } } + + private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWriter writer) + { + if (codeElement.OriginalCodeElement is not CodeClass codeClass) throw new InvalidOperationException("Original CodeElement cannot be null"); + var executorMethods = codeClass.Methods + .Where(static x => x.Kind is CodeMethodKind.RequestExecutor) + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (executorMethods.Length == 0) + return; + writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: Record = {{"); + foreach (var executorMethod in executorMethods) + { + var returnType = conventions.GetTypeString(executorMethod.ReturnType, codeElement); + var isVoid = "void".EqualsIgnoreCase(returnType); + var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); + var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); + writer.StartBlock($"\"{executorMethod.Name.ToFirstCharacterLowerCase()}\": {{"); + if (executorMethod.AcceptHeaderValue is string acceptHeader && !string.IsNullOrEmpty(acceptHeader)) + writer.WriteLine($"responseBodyContentType: \"{acceptHeader}\","); + if (executorMethod.ErrorMappings.Any()) + { + writer.StartBlock("errorMappings: {"); + foreach (var errorMapping in executorMethod.ErrorMappings) + { + writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {GetFactoryMethodName(errorMapping.Value, codeElement, writer)},"); + } + writer.CloseBlock("} as Record>,"); + } + writer.WriteLine($"adapterMethodName: \"{GetSendRequestMethodName(isVoid, isStream, executorMethod.ReturnType.IsCollection, returnTypeWithoutCollectionSymbol)}\","); + if (!isVoid) + writer.WriteLine($"responseBodyFactory: {GetTypeFactory(isVoid, isStream, executorMethod, writer)},"); + if (!string.IsNullOrEmpty(executorMethod.RequestBodyContentType)) + writer.WriteLine($"requestBodyContentType: \"{executorMethod.RequestBodyContentType}\","); //TODO add support for configurable content type + if (executorMethod.Parameters.FirstOrDefault(static x => x.Kind is CodeParameterKind.RequestBody) is CodeParameter requestBody && GetBodySerializer(requestBody) is string bodySerializer) + writer.WriteLine($"requestBodySerializer: {bodySerializer},"); + if (codeElement.Parent is CodeFile parentCodeFile && + parentCodeFile.FindChildByName(codeElement.Name.Replace("RequestsMetadata", $"{executorMethod.Name.ToFirstCharacterUpperCase()}QueryParametersMapper", StringComparison.Ordinal), false) is CodeConstant mapperConstant) + writer.WriteLine($"queryParametersMapper: {mapperConstant.Name.ToFirstCharacterUpperCase()},"); + writer.CloseBlock("},"); + } + writer.CloseBlock("};"); + } + private string? GetBodySerializer(CodeParameter requestBody) + { + if (requestBody.Type is CodeType currentType && (currentType.TypeDefinition is CodeInterface || currentType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) + { + return $"serialize{currentType.Name.ToFirstCharacterUpperCase()}"; + } + return default; + } + private string GetTypeFactory(bool isVoid, bool isStream, CodeMethod codeElement, LanguageWriter writer) + { + if (isVoid) return string.Empty; + var typeName = conventions.TranslateType(codeElement.ReturnType); + if (isStream || conventions.IsPrimitiveType(typeName)) return $" \"{typeName}\""; + return $" {GetFactoryMethodName(codeElement.ReturnType, codeElement, writer)}"; + } + private string GetReturnTypeWithoutCollectionSymbol(CodeMethod codeElement, string fullTypeName) + { + if (!codeElement.ReturnType.IsCollection) return fullTypeName; + var clone = (CodeTypeBase)codeElement.ReturnType.Clone(); + 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, string returnType) + { + if (isVoid) return "sendNoResponseContentAsync"; + if (isCollection) + { + if (conventions.IsPrimitiveType(returnType)) return $"sendCollectionOfPrimitiveAsync"; + return $"sendCollectionAsync"; + } + + if (isStream || conventions.IsPrimitiveType(returnType)) return $"sendPrimitiveAsync"; + return $"sendAsync"; + } + + private void WriteUriTemplateConstant(CodeConstant codeElement, LanguageWriter writer) + { + writer.WriteLine($"export const {codeElement.Name.ToFirstCharacterUpperCase()} = {codeElement.UriTemplate};"); + } + private static void WriteQueryParametersMapperConstant(CodeConstant codeElement, LanguageWriter writer) { if (codeElement.OriginalCodeElement is not CodeInterface codeInterface) throw new InvalidOperationException("Original CodeElement cannot be null"); - writer.StartBlock($"const {codeElement.Name.ToFirstCharacterLowerCase()}: Record = {{"); - foreach (var property in codeInterface - .Properties - .OfKind(CodePropertyKind.QueryParameter) - .Where(static x => !string.IsNullOrEmpty(x.SerializationName)) - .OrderBy(static x => x.SerializationName, StringComparer.OrdinalIgnoreCase)) + if (codeInterface.Properties + .OfKind(CodePropertyKind.QueryParameter) + .Where(static x => !string.IsNullOrEmpty(x.SerializationName)) + .OrderBy(static x => x.SerializationName, StringComparer.OrdinalIgnoreCase) + .ToArray() is not { Length: > 0 } properties) + return; + writer.StartBlock($"const {codeElement.Name.ToFirstCharacterUpperCase()}: Record = {{"); + foreach (var property in properties) { writer.WriteLine($"\"{property.Name.ToFirstCharacterLowerCase()}\": \"{property.SerializationName}\","); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index 1d42abd261..a1d4478563 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -30,7 +30,15 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w private static void WriteCodePropertyForInterface(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) { - writer.WriteLine($"{codeElement.Name.ToFirstCharacterLowerCase()}?: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); + switch (codeElement.Kind) + { + case CodePropertyKind.RequestBuilder: + writer.WriteLine($"get {codeElement.Name.ToFirstCharacterLowerCase()}(): {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); + break; + default: + writer.WriteLine($"{codeElement.Name.ToFirstCharacterLowerCase()}?: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); + break; + } } private void WriteCodePropertyForClass(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) From b8b49f68c078930a62affcddb0eaa2ca1bea1abb Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 13:30:00 -0500 Subject: [PATCH 04/40] - adds a search depth limit to find by name Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 8 ++++++-- src/Kiota.Builder/CodeDOM/IBlock.cs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index 6a7e80c9cb..9da2ec86ee 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -157,6 +157,10 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle return Enumerable.Empty(); } public T? FindChildByName(string childName, bool findInChildElements = true) where T : ICodeElement + { + return FindChildByName(childName, findInChildElements ? uint.MaxValue : 1); + } + public T? FindChildByName(string childName, uint maxDepth) where T : ICodeElement { ArgumentException.ThrowIfNullOrEmpty(childName); @@ -165,10 +169,10 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle if (InnerChildElements.TryGetValue(childName, out var result) && result is T castResult) return castResult; - if (findInChildElements) + if (--maxDepth > 0) foreach (var childElement in InnerChildElements.Values.OfType()) { - var childResult = childElement.FindChildByName(childName); + var childResult = childElement.FindChildByName(childName, maxDepth); if (childResult != null) return childResult; } diff --git a/src/Kiota.Builder/CodeDOM/IBlock.cs b/src/Kiota.Builder/CodeDOM/IBlock.cs index 2dd604bd98..dd6618c601 100644 --- a/src/Kiota.Builder/CodeDOM/IBlock.cs +++ b/src/Kiota.Builder/CodeDOM/IBlock.cs @@ -9,6 +9,7 @@ namespace Kiota.Builder.CodeDOM; public interface IBlock { T? FindChildByName(string childName, bool findInChildElements = true) where T : ICodeElement; + T? FindChildByName(string childName, uint maxDepth) where T : ICodeElement; IEnumerable FindChildrenByName(string childName) where T : ICodeElement; void AddUsing(params CodeUsing[] codeUsings); CodeElement? Parent From e683c805f5f8574c034beb062c10bf74081ecc9f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 13:31:28 -0500 Subject: [PATCH 05/40] - implements navigation metadata generation Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 1 + .../Refiners/TypeScriptRefiner.cs | 67 +++++++++++++++---- .../Writers/TypeScript/CodeConstantWriter.cs | 64 ++++++++++++++++-- 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index dbe9653e1d..f823c8e8cd 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -9,6 +9,7 @@ public class CodeFile : CodeBlock public IEnumerable Interfaces => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); public IEnumerable Classes => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); public IEnumerable Enums => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); + public IEnumerable Constants => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); public IEnumerable AddElements(params T[] elements) where T : CodeElement { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 10f2a9a6c9..5f648561f1 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -201,13 +201,14 @@ private void GenerateReusableModelsCodeFiles(CodeElement currentElement) private static void GenerateRequestBuilderCodeFiles(CodeNamespace modelsNamespace) { if (modelsNamespace.Parent is not CodeNamespace mainNamespace) return; - foreach (var subNamespace in mainNamespace.Namespaces.Except([modelsNamespace])) + var elementsToConsider = mainNamespace.Namespaces.Except([modelsNamespace]).OfType().Union(mainNamespace.Classes).ToArray(); + foreach (var element in elementsToConsider) { - GenerateRequestBuilderCodeFilesForElement(subNamespace); + GenerateRequestBuilderCodeFilesForElement(element); } - foreach (var classElement in mainNamespace.Classes) - { - GenerateRequestBuilderCodeFilesForElement(classElement); + 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) @@ -216,6 +217,28 @@ private static void GenerateRequestBuilderCodeFilesForElement(CodeElement curren GenerateRequestBuilderCodeFile(ReplaceRequestBuilderClassByInterface(currentClass, codeNamespace), codeNamespace); CrawlTree(currentElement, GenerateRequestBuilderCodeFilesForElement); } + private static void AddDownwardsConstantsImports(CodeElement currentElement) + { + if (currentElement is CodeInterface currentInterface && + currentInterface.Kind is CodeInterfaceKind.RequestBuilder && + currentElement.Parent is CodeFile codeFile && + codeFile.Parent is CodeNamespace parentNamespace && + parentNamespace.Parent is CodeNamespace parentLevelNamespace && + parentLevelNamespace.Files.SelectMany(static x => x.Interfaces).FirstOrDefault(static x => x.Kind is CodeInterfaceKind.RequestBuilder) is CodeInterface parentLevelInterface && + codeFile.Constants + .Where(static x => x.Kind is CodeConstantKind.NavigationMetadata or CodeConstantKind.UriTemplate or CodeConstantKind.RequestsMetadata) + .Select(static x => new CodeUsing + { + Name = x.Name, + Declaration = new CodeType + { + TypeDefinition = x, + }, + }) + .ToArray() is { Length: > 0 } constantUsings) + parentLevelInterface.AddUsing(constantUsings); + CrawlTree(currentElement, AddDownwardsConstantsImports); + } private static CodeFile? GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { @@ -233,6 +256,31 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && return null; return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); } + private static readonly CodeUsing[] requestBuilderUsings = [ + new CodeUsing { + Name = "RequestMetadata", + IsErasable = true, + Declaration = new CodeType { + Name = AbstractionsPackageName, + IsExternal = true, + }, + }, + new CodeUsing { + Name = "NavigationMetadata", + IsErasable = true, + Declaration = new CodeType { + Name = AbstractionsPackageName, + IsExternal = true, + }, + }, + new CodeUsing { + Name = "KeysToExcludeForNavigationMetadata", + IsErasable = true, + Declaration = new CodeType { + Name = AbstractionsPackageName, + IsExternal = true, + }, + }]; private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass codeClass, CodeNamespace codeNamespace) { if (CodeConstant.FromRequestBuilderToRequestsMetadata(codeClass) is CodeConstant requestsMetadataConstant) @@ -241,14 +289,7 @@ private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass cod codeNamespace.AddConstant(navigationConstant); if (CodeConstant.FromRequestBuilderClassToUriTemplate(codeClass) is CodeConstant uriTemplateConstant) codeNamespace.AddConstant(uriTemplateConstant); - var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass, [new CodeUsing { - Name = "RequestMetadata", - IsErasable = true, - Declaration = new CodeType { - Name = AbstractionsPackageName, - IsExternal = true, - }, - }]); + var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass, requestBuilderUsings); codeNamespace.RemoveChildElement(codeClass); codeNamespace.AddInterface(interfaceDeclaration); return interfaceDeclaration; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 98d40cf2d9..57eadda64c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -25,18 +25,70 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w case CodeConstantKind.RequestsMetadata: WriteRequestsMetadataConstant(codeElement, writer); break; - //TODO new constant types + case CodeConstantKind.NavigationMetadata: + WriteNavigationMetadataConstant(codeElement, writer); + break; + default: + throw new InvalidOperationException($"Invalid constant kind {codeElement.Kind}"); + } + } + + private void WriteNavigationMetadataConstant(CodeConstant codeElement, LanguageWriter writer) + { + if (codeElement.OriginalCodeElement is not CodeClass codeClass) throw new InvalidOperationException("Original CodeElement cannot be null"); + if (codeElement.Parent is not CodeFile parentCodeFile || parentCodeFile.FindChildByName(codeElement.Name.Replace("NavigationMetadata", string.Empty, StringComparison.Ordinal), false) is not CodeInterface currentInterface) + throw new InvalidOperationException("Couldn't find the associated interface for the navigation metadata constant"); + var navigationMethods = codeClass.Methods + .Where(static x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters) + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + var navigationProperties = codeClass.Properties + .Where(static x => x.Kind is CodePropertyKind.RequestBuilder) + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (navigationProperties.Length == 0 && navigationMethods.Length == 0) + return; + + var parentNamespace = codeElement.GetImmediateParentOfType(); + writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: Record, NavigationMetadata> = {{"); + foreach (var navigationMethod in navigationMethods) + { + writer.StartBlock($"\"{navigationMethod.Name.ToFirstCharacterLowerCase()}\": {{"); + var requestBuilderName = navigationMethod.ReturnType.Name.ToFirstCharacterUpperCase(); + WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName, navigationMethod.Parameters.Where(static x => x.Kind is CodeParameterKind.Path && !string.IsNullOrEmpty(x.SerializationName)).Select(static x => $"\"{x.SerializationName}\"").ToArray()); + writer.CloseBlock("},"); + } + foreach (var navigationProperty in navigationProperties) + { + writer.StartBlock($"\"{navigationProperty.Name.ToFirstCharacterLowerCase()}\": {{"); + var requestBuilderName = navigationProperty.Type.Name.ToFirstCharacterUpperCase(); + WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName); + writer.CloseBlock("},"); + } + writer.CloseBlock("};"); + } + + private static void WriteNavigationMetadataEntry(CodeNamespace parentNamespace, LanguageWriter writer, string requestBuilderName, string[]? pathParameters = null) + { + if (parentNamespace.FindChildByName($"{requestBuilderName}UriTemplate", 3) is CodeConstant uriTemplateConstant && uriTemplateConstant.Kind is CodeConstantKind.UriTemplate) + writer.WriteLine($"uriTemplate: {uriTemplateConstant.Name.ToFirstCharacterUpperCase()},"); + if (parentNamespace.FindChildByName($"{requestBuilderName}RequestsMetadata", 3) is CodeConstant requestsMetadataConstant && requestsMetadataConstant.Kind is CodeConstantKind.RequestsMetadata) + writer.WriteLine($"requestsMetadata: {requestsMetadataConstant.Name.ToFirstCharacterUpperCase()},"); + if (parentNamespace.FindChildByName($"{requestBuilderName}NavigationMetadata", 3) is CodeConstant navigationMetadataConstant && navigationMetadataConstant.Kind is CodeConstantKind.NavigationMetadata) + writer.WriteLine($"navigationMetadata: {navigationMetadataConstant.Name.ToFirstCharacterUpperCase()},"); + if (pathParameters is { Length: > 0 }) + { + writer.StartBlock($"pathSegmentOfPathParameters: [{string.Join(", ", pathParameters)}],"); } } private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWriter writer) { if (codeElement.OriginalCodeElement is not CodeClass codeClass) throw new InvalidOperationException("Original CodeElement cannot be null"); - var executorMethods = codeClass.Methods - .Where(static x => x.Kind is CodeMethodKind.RequestExecutor) - .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) - .ToArray(); - if (executorMethods.Length == 0) + if (codeClass.Methods + .Where(static x => x.Kind is CodeMethodKind.RequestExecutor) + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) + .ToArray() is not { Length: > 0 } executorMethods) return; writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: Record = {{"); foreach (var executorMethod in executorMethods) From 2896b6755a9eead40b4a31679af8567b55c85b63 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 13:49:01 -0500 Subject: [PATCH 06/40] - fixes a bug where the file for the client request builder would have the wrong name in typescript Signed-off-by: Vincent Biret --- src/Kiota.Builder/PathSegmenters/TypeScriptPathSegmenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/PathSegmenters/TypeScriptPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/TypeScriptPathSegmenter.cs index 257186d451..1b3f69b748 100644 --- a/src/Kiota.Builder/PathSegmenters/TypeScriptPathSegmenter.cs +++ b/src/Kiota.Builder/PathSegmenters/TypeScriptPathSegmenter.cs @@ -21,7 +21,7 @@ public override string NormalizeFileName(CodeElement currentElement) CodeFile currentFile when modelsNamespace is not null && currentElement.GetImmediateParentOfType() is CodeNamespace currentNamespace && !(modelsNamespace.IsParentOf(currentNamespace) || modelsNamespace == currentNamespace) && - !currentFile.Classes.Any(static x => x.Kind is CodeClassKind.RequestBuilder && x.Methods.Any(static y => y.Kind is CodeMethodKind.ClientConstructor)) + !currentFile.Interfaces.Any(static x => x.Kind is CodeInterfaceKind.RequestBuilder && x.OriginalClass is not null && x.OriginalClass.Methods.Any(static y => y.Kind is CodeMethodKind.ClientConstructor)) => IndexFileName, _ => GetDefaultFileName(currentElement), }; From 157af6061e2da717f630ad688dd875bd4b8d12ef Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 13:56:07 -0500 Subject: [PATCH 07/40] - adds base request builder for TS request builders Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeInterface.cs | 2 ++ src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs | 5 +++-- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 11 ++++++++++- .../Writers/TypeScript/CodeClassDeclarationWriter.cs | 2 +- .../TypeScript/CodeInterfaceDeclarationWriter.cs | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index f2d077d4bb..64f1268974 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -51,6 +51,8 @@ CodeMethodKind.IndexerBackwardCompatibility or result.AddUsing(usings); //TODO pass a list of external imports to remove as we create the interface if (usingsToAdd is { Length: > 0 } usingsToAddList) result.AddUsing(usingsToAddList); + if (codeClass.StartBlock.Inherits is not null) + result.StartBlock.AddImplements(codeClass.StartBlock.Inherits); return result; } } diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 9456c4dbb7..8220666d32 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -1379,11 +1379,12 @@ mappingClass.Parent is CodeNamespace mappingNamespace && } CrawlTree(currentElement, RemoveDiscriminatorMappingsTargetingSubNamespaces); } - protected static void MoveRequestBuilderPropertiesToBaseType(CodeElement currentElement, CodeUsing? baseTypeUsing = null, AccessModifier? accessModifier = null, bool addCurrentTypeAsGenericTypeParameter = false) + protected static void MoveRequestBuilderPropertiesToBaseType(CodeElement currentElement, CodeUsing baseTypeUsing, AccessModifier? accessModifier = null, bool addCurrentTypeAsGenericTypeParameter = false) { + ArgumentNullException.ThrowIfNull(baseTypeUsing); if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder)) { - if (currentClass.StartBlock.Inherits is null && baseTypeUsing is not null) + if (currentClass.StartBlock.Inherits == null) { currentClass.StartBlock.Inherits = new CodeType { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 5f648561f1..ce3cfa13d1 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -20,7 +20,16 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlConstructor, CodeMethodKind.RawUrlBuilder); ReplaceReservedNames(generatedCode, new TypeScriptReservedNamesProvider(), static x => $"{x}Escaped"); ReplaceReservedExceptionPropertyNames(generatedCode, new TypeScriptExceptionsReservedNamesProvider(), static x => $"{x}Escaped"); - MoveRequestBuilderPropertiesToBaseType(generatedCode); + MoveRequestBuilderPropertiesToBaseType(generatedCode, + new CodeUsing + { + Name = "BaseRequestBuilder", + Declaration = new CodeType + { + Name = AbstractionsPackageName, + IsExternal = true + } + }, addCurrentTypeAsGenericTypeParameter: true); ReplaceIndexersByMethodsWithParameter(generatedCode, false, static x => $"by{x.ToFirstCharacterUpperCase()}", diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs index 06c318ff7e..34f6c5285a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs @@ -25,7 +25,7 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit var inheritSymbol = codeElement.Inherits is null ? string.Empty : conventions.GetTypeString(codeElement.Inherits, codeElement); var derivation = (string.IsNullOrEmpty(inheritSymbol) ? string.Empty : $" extends {inheritSymbol}") + - (!codeElement.Implements.Any() ? string.Empty : $" implements {codeElement.Implements.Select(x => x.Name).Aggregate((x, y) => x + ", " + y)}"); + (!codeElement.Implements.Any() ? string.Empty : $" implements {codeElement.Implements.Select(static x => x.Name).Aggregate(static (x, y) => x + ", " + y)}"); if (codeElement.Parent is CodeClass parentClass) conventions.WriteLongDescription(parentClass, writer); writer.WriteLine($"export class {codeElement.Name.ToFirstCharacterUpperCase()}{derivation} {{"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs index 5b69f75270..819555eca8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs @@ -26,7 +26,7 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language } if (codeElement.Parent is CodeInterface parentInterface) conventions.WriteLongDescription(parentInterface, writer); - var derivation = codeElement.Implements.Any() ? $" extends {codeElement.Implements.Select(static x => x.Name).Aggregate(static (x, y) => x + ", " + y)}" : string.Empty; + var derivation = codeElement.Implements.Any() ? $" extends {codeElement.Implements.Select(x => conventions.GetTypeString(x, codeElement)).Aggregate(static (x, y) => x + ", " + y)}" : string.Empty; writer.StartBlock($"export interface {codeElement.Name.ToFirstCharacterUpperCase()}{derivation} {{"); } } From b72b41009c12e4395c8ed84a81fa9a2591fafca2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 14:24:38 -0500 Subject: [PATCH 08/40] - marks imports as erasable Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeInterface.cs | 6 ++++++ src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index 64f1268974..c55a091cb9 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -48,7 +48,13 @@ CodeMethodKind.IndexerBackwardCompatibility or result.AddProperty(properties); if (codeClass.Usings.ToArray() is { Length: > 0 } usings) + { + foreach (var usingToCopy in usings.Where(static x => x.Declaration is not null && x.Declaration.TypeDefinition is CodeInterface or CodeClass { Kind: CodeClassKind.RequestBuilder })) + { + usingToCopy.IsErasable = true; + } result.AddUsing(usings); //TODO pass a list of external imports to remove as we create the interface + } if (usingsToAdd is { Length: > 0 } usingsToAddList) result.AddUsing(usingsToAddList); if (codeClass.StartBlock.Inherits is not null) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index ce3cfa13d1..1877c351a5 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -24,6 +24,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance new CodeUsing { Name = "BaseRequestBuilder", + IsErasable = true, Declaration = new CodeType { Name = AbstractionsPackageName, From 15e7941f668db880af93426d6abe17a96bc3da4c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 14:24:50 -0500 Subject: [PATCH 09/40] - fixes path parameters mapping Signed-off-by: Vincent Biret --- src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 57eadda64c..b3a18495a8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -55,7 +55,7 @@ private void WriteNavigationMetadataConstant(CodeConstant codeElement, LanguageW { writer.StartBlock($"\"{navigationMethod.Name.ToFirstCharacterLowerCase()}\": {{"); var requestBuilderName = navigationMethod.ReturnType.Name.ToFirstCharacterUpperCase(); - WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName, navigationMethod.Parameters.Where(static x => x.Kind is CodeParameterKind.Path && !string.IsNullOrEmpty(x.SerializationName)).Select(static x => $"\"{x.SerializationName}\"").ToArray()); + WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName, navigationMethod.Parameters.Where(static x => x.Kind is CodeParameterKind.Path or CodeParameterKind.Custom && !string.IsNullOrEmpty(x.SerializationName)).Select(static x => $"\"{x.SerializationName}\"").ToArray()); writer.CloseBlock("},"); } foreach (var navigationProperty in navigationProperties) @@ -77,9 +77,7 @@ private static void WriteNavigationMetadataEntry(CodeNamespace parentNamespace, if (parentNamespace.FindChildByName($"{requestBuilderName}NavigationMetadata", 3) is CodeConstant navigationMetadataConstant && navigationMetadataConstant.Kind is CodeConstantKind.NavigationMetadata) writer.WriteLine($"navigationMetadata: {navigationMetadataConstant.Name.ToFirstCharacterUpperCase()},"); if (pathParameters is { Length: > 0 }) - { - writer.StartBlock($"pathSegmentOfPathParameters: [{string.Join(", ", pathParameters)}],"); - } + writer.WriteLine($"pathParametersMappings: [{string.Join(", ", pathParameters)}],"); } private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWriter writer) From 0f682c052b542d3f1948d7da375493fe5ab5eae9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 15:15:21 -0500 Subject: [PATCH 10/40] - adds support for client constructor through proxy Signed-off-by: Vincent Biret --- .../Refiners/TypeScriptRefiner.cs | 18 ++++++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 43 ++++++++++++++++--- .../Writers/TypeScript/CodeMethodWriter.cs | 36 +++------------- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 1877c351a5..73ec31cde8 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -299,6 +299,21 @@ private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass cod codeNamespace.AddConstant(navigationConstant); if (CodeConstant.FromRequestBuilderClassToUriTemplate(codeClass) is CodeConstant uriTemplateConstant) codeNamespace.AddConstant(uriTemplateConstant); + if (codeClass.Methods.FirstOrDefault(static x => x.Kind is CodeMethodKind.ClientConstructor) is CodeMethod clientConstructor) + { + clientConstructor.IsStatic = true; + clientConstructor.Name = $"New{codeClass.Name.ToFirstCharacterUpperCase()}"; + + codeNamespace.AddFunction(new CodeFunction(clientConstructor)).First().AddUsing(new CodeUsing + { + Name = "apiClientProxifier", + Declaration = new CodeType + { + Name = AbstractionsPackageName, + IsExternal = true, + }, + }); + } var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass, requestBuilderUsings); codeNamespace.RemoveChildElement(codeClass); codeNamespace.AddInterface(interfaceDeclaration); @@ -351,8 +366,11 @@ private static void GenerateRequestBuilderCodeFile(CodeInterface codeInterface, .OfType() .ToArray(); + var clientConstructorFunction = codeNamespace.FindChildByName($"New{codeInterface.Name.ToFirstCharacterUpperCase()}", false); + codeNamespace.RemoveChildElement(inlineRequestAndResponseBodyFiles); var elements = new CodeElement[] { codeInterface } + .Union(clientConstructorFunction is not null ? new[] { clientConstructorFunction } : Array.Empty()) .Union(proxyConstants) .Union(queryParameterInterfaces) .Union(queryParametersMapperConstants) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 333df72eb2..edda560e59 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -24,17 +24,12 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w if (codeElement.OriginalLocalMethod == null) throw new InvalidOperationException($"{nameof(codeElement.OriginalLocalMethod)} should not be null"); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is not CodeNamespace && codeElement.Parent is not CodeFile) throw new InvalidOperationException("the parent of a function should be a namespace or file"); - - if (codeElement.Parent is CodeNamespace) - { - conventions.WriteAutoGeneratedStart(writer); - _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), writer); - } + if (codeElement.Parent is not CodeFile parentFile) throw new InvalidOperationException("the parent of a function should be a file"); var codeMethod = codeElement.OriginalLocalMethod; var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty; + //TODO documentation is missing CodeMethodWriter.WriteMethodPrototypeInternal(codeElement.OriginalLocalMethod, writer, returnType, false, conventions, true); writer.IncreaseIndent(); @@ -50,10 +45,44 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w case CodeMethodKind.Factory: WriteDiscriminatorFunction(codeElement, writer); break; + case CodeMethodKind.ClientConstructor: + WriteApiConstructorBody(parentFile, codeMethod, writer); + break; default: throw new InvalidOperationException("Invalid code method kind"); } } + private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod method, LanguageWriter writer) + { + WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); + WriteSerializationRegistration(method.DeserializerModules, writer, "registerDefaultDeserializer"); + 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.WriteLine($"{requestAdapterArgumentName}.baseUrl = \"{method.BaseUrl}\";"); + writer.CloseBlock(); + } + writer.StartBlock($"const pathParameters: Record = {{"); + writer.WriteLine($"\"baseurl\": {requestAdapterArgumentName}.baseUrl,"); + writer.CloseBlock("};"); + if (method.Parameters.OfKind(CodeParameterKind.BackingStore)?.Name is string backingStoreParameterName) + writer.WriteLine($"{requestAdapterArgumentName}.enableBackingStore({backingStoreParameterName.ToFirstCharacterLowerCase()});"); + if (parentFile.Interfaces.FirstOrDefault(static x => x.Kind is CodeInterfaceKind.RequestBuilder) is CodeInterface codeInterface) + { + var uriTemplateConstantName = $"{codeInterface.Name.ToFirstCharacterUpperCase()}UriTemplate"; + var navigationMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}NavigationMetadata", false) is { } navConstant ? navConstant.Name.ToFirstCharacterUpperCase() : "undefined"; + var requestsMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}RequestsMetadata", false) is { } reqConstant ? reqConstant.Name.ToFirstCharacterUpperCase() : "undefined"; + writer.WriteLine($"return apiClientProxifier<{codeInterface.Name.ToFirstCharacterUpperCase()}>({requestAdapterArgumentName}, pathParameters, {uriTemplateConstantName}, {navigationMetadataConstantName}, {requestsMetadataConstantName});"); + } + } + private static void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, string methodName) + { + if (serializationModules != null) + foreach (var module in serializationModules) + writer.WriteLine($"{methodName}({module});"); + } + private void WriteDiscriminatorFunction(CodeFunction codeElement, LanguageWriter writer) { var returnType = conventions.GetTypeString(codeElement.OriginalLocalMethod.ReturnType, codeElement); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 4662e08c36..752dae2d3a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -51,11 +51,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.RawUrlBuilder: throw new InvalidOperationException("RawUrlBuilder is implemented in the base type in TypeScript."); case CodeMethodKind.ClientConstructor: - WriteConstructorBody(parentClass, codeElement, writer, inherits); - WriteApiConstructorBody(parentClass, codeElement, writer); - break; + throw new InvalidOperationException("ClientConstructor is implemented in by a function in TypeScript."); case CodeMethodKind.Constructor: - WriteConstructorBody(parentClass, codeElement, writer, inherits); + WriteConstructorBody(parentClass, codeElement, writer, inherits); //TODO double check whether we still need this break; case CodeMethodKind.RequestBuilderWithParameters: throw new InvalidOperationException("RequestBuilderWithParameters is implemented by constants in TypeScript."); @@ -70,10 +68,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri case CodeMethodKind.ErrorMessageOverride: throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by the deserializer function in typescript."); default: - WriteDefaultMethodBody(codeElement, writer); + WriteDefaultMethodBody(codeElement, writer); //TODO double check whether we still need this break; } - writer.CloseBlock(); + writer.CloseBlock(); //TODO if all cases are not needed anymore, drop the class } } @@ -92,30 +90,7 @@ internal static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWr writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); } } - private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) - { - var pathParametersProperty = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters); - var backingStoreParameter = method.Parameters.FirstOrDefault(static x => x.IsOfKind(CodeParameterKind.BackingStore)); - WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); - WriteSerializationRegistration(method.DeserializerModules, writer, "registerDefaultDeserializer"); - if (parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter)?.Name.ToFirstCharacterLowerCase() is not string requestAdapterPropertyName) return; - if (!string.IsNullOrEmpty(method.BaseUrl)) - { - writer.StartBlock($"if ({requestAdapterPropertyName}.baseUrl === undefined || {requestAdapterPropertyName}.baseUrl === \"\") {{"); - writer.WriteLine($"{requestAdapterPropertyName}.baseUrl = \"{method.BaseUrl}\";"); - writer.CloseBlock(); - if (pathParametersProperty != null) - writer.WriteLine($"this.{pathParametersProperty.Name.ToFirstCharacterLowerCase()}[\"baseurl\"] = {requestAdapterPropertyName}.baseUrl;"); - } - if (backingStoreParameter != null) - writer.WriteLine($"this.{requestAdapterPropertyName}.enableBackingStore({backingStoreParameter.Name});"); - } - private static void WriteSerializationRegistration(HashSet serializationModules, LanguageWriter writer, string methodName) - { - if (serializationModules != null) - foreach (var module in serializationModules) - writer.WriteLine($"{methodName}({module});"); - } + private CodePropertyKind[]? _DirectAccessProperties; private CodePropertyKind[] DirectAccessProperties { @@ -269,7 +244,6 @@ internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWrite var methodName = (code.Kind switch { _ when code.IsAccessor => code.AccessedProperty?.Name, - _ when isConstructor => "constructor", _ => code.Name, })?.ToFirstCharacterLowerCase(); var asyncPrefix = code.IsAsync && code.Kind != CodeMethodKind.RequestExecutor ? " async " : string.Empty; From c047245b72cbfa3cd9c9c345cdd44d6a9a85f104 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 15:38:27 -0500 Subject: [PATCH 11/40] - fixes missing accept header value Signed-off-by: Vincent Biret --- src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index b3a18495a8..52c0f890e6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -96,7 +96,8 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); writer.StartBlock($"\"{executorMethod.Name.ToFirstCharacterLowerCase()}\": {{"); - if (executorMethod.AcceptHeaderValue is string acceptHeader && !string.IsNullOrEmpty(acceptHeader)) + if (codeClass.Methods.FirstOrDefault(x => x.Kind is CodeMethodKind.RequestGenerator && x.HttpMethod == executorMethod.HttpMethod) is { } generatorMethod && + generatorMethod.AcceptHeaderValue is string acceptHeader && !string.IsNullOrEmpty(acceptHeader)) writer.WriteLine($"responseBodyContentType: \"{acceptHeader}\","); if (executorMethod.ErrorMappings.Any()) { From ec7c914d97e9c0f1add069bbc61593e03c7fbcb3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 12 Dec 2023 15:52:24 -0500 Subject: [PATCH 12/40] - typescript imports cleanup Signed-off-by: Vincent Biret --- .../Refiners/TypeScriptRefiner.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 73ec31cde8..ef04c962bb 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -500,31 +500,31 @@ private static bool HasMultipartBody(CodeMethod m) => m.Parameters.Any(IsMultipartBody); // for Kiota abstraction library if the code is not required for runtime purposes e.g. interfaces then the IsErassable flag is set to true private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = { - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor, AbstractionsPackageName, true, "RequestAdapter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), - AbstractionsPackageName, false, "RequestInformation"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.RequestGenerator, + AbstractionsPackageName, true, "RequestInformation"), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.Serializer, AbstractionsPackageName, true,"SerializationWriter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Factory), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.Deserializer or CodeMethodKind.Factory, AbstractionsPackageName, true, "ParseNode"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.RequestExecutor, AbstractionsPackageName, true, "Parsable", "ParsableFactory"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model), + new (static x => x is CodeClass @class && @class.Kind is CodeClassKind.Model, AbstractionsPackageName, true, "Parsable"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && @class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)), + new (static x => x is CodeClass @class && @class.Kind is CodeClassKind.Model && @class.Properties.Any(static x => x.Kind is CodePropertyKind.AdditionalData), AbstractionsPackageName, true, "AdditionalDataHolder"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) && - method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor && + method.Parameters.Any(static y => y.Kind is CodeParameterKind.BackingStore), AbstractionsPackageName, true, "BackingStoreFactory"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) && - method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)), + new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor && + method.Parameters.Any(static y => y.Kind is CodeParameterKind.BackingStore), AbstractionsPackageName, false, "BackingStoreFactorySingleton"), - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), + new (static x => x is CodeProperty prop && prop.Kind is CodePropertyKind.BackingStore, AbstractionsPackageName, true, "BackingStore", "BackedModel"), - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore), + new (static x => x is CodeProperty prop && prop.Kind is CodePropertyKind.BackingStore, AbstractionsPackageName, false, "BackingStoreFactorySingleton"), - new (x => x is CodeMethod m && HasMultipartBody(m), + new (static x => x is CodeMethod m && HasMultipartBody(m), AbstractionsPackageName, MultipartBodyClassName, $"serialize{MultipartBodyClassName}") }; private const string MultipartBodyClassName = "MultipartBody"; From 7bdaea299f326fe46b80d7831cf4531197acbd41 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 15 Dec 2023 13:22:54 -0500 Subject: [PATCH 13/40] - moves tests to the right location after typescript constants change Signed-off-by: Vincent Biret --- .../TypeScript/CodeConstantWriterTests.cs | 330 ++++++++++++- .../TypeScript/CodeFunctionWriterTests.cs | 176 +++++++ .../TypeScript/CodeMethodWriterTests.cs | 453 +----------------- .../TypeScript/CodePropertyWriterTests.cs | 6 +- 4 files changed, 504 insertions(+), 461 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index c61bcc422f..49d29081b6 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -3,8 +3,8 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Writers; -using Kiota.Builder.Writers.Go; using Kiota.Builder.Writers.TypeScript; +using Kiota.Builder.Extensions; using Xunit; @@ -15,17 +15,35 @@ public sealed class CodeConstantWriterTests : IDisposable private const string DefaultName = "name"; private readonly StringWriter tw; private readonly LanguageWriter writer; - private readonly CodeEnum currentEnum; + private CodeEnum currentEnum; private const string EnumName = "someEnum"; private readonly CodeConstantWriter codeConstantWriter; - + private readonly CodeNamespace root = CodeNamespace.InitRootNamespace(); + private readonly CodeClass parentClass; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; public CodeConstantWriterTests() { writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); codeConstantWriter = new CodeConstantWriter(new()); tw = new StringWriter(); writer.SetTextWriter(tw); - var root = CodeNamespace.InitRootNamespace(); + parentClass = root.AddClass(new CodeClass + { + Name = "parentClass", + Kind = CodeClassKind.RequestBuilder + }).First(); + method = parentClass.AddMethod(new CodeMethod + { + Name = MethodName, + ReturnType = new CodeType + { + Name = ReturnTypeName + } + }).First(); + } + private void AddCodeEnum() + { currentEnum = root.AddEnum(new CodeEnum { Name = EnumName, @@ -65,6 +83,7 @@ public void WriteCodeElement_ThrowsException_WhenOriginalCodeElementIsNull() [Fact] public void WritesEnumOptionDescription() { + AddCodeEnum(); var option = new CodeEnumOption { Documentation = new() @@ -83,6 +102,7 @@ public void WritesEnumOptionDescription() [Fact] public void WritesEnum() { + AddCodeEnum(); const string optionName = "option1"; currentEnum.AddOption(new CodeEnumOption { Name = optionName }); codeConstantWriter.WriteCodeElement(currentEnum.CodeEnumObject, writer); @@ -97,8 +117,310 @@ public void WritesEnum() [Fact] public void DoesntWriteAnythingOnNoOption() { + AddCodeEnum(); codeConstantWriter.WriteCodeElement(currentEnum.CodeEnumObject, writer); var result = tw.ToString(); Assert.Empty(result); } + private readonly CodeMethod method; + [Fact] + public void DoesNotCreateDictionaryOnEmptyErrorMapping() + { + method.Kind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + + AddRequestBodyParameters(); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.DoesNotContain("errorMappings", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyForMultipart() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(); + method.Parameters.First(static x => x.IsOfKind(CodeParameterKind.RequestBody)).Type = new CodeType { Name = "MultipartBody", IsExternal = true }; + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("setContentFromParsable", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestExecutorBodyForCollections() + { + method.Kind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + AddRequestBodyParameters(); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("sendCollectionAsync", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyForScalar() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(); + method.AcceptedResponseTypes.Add("application/json"); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters)", result); + Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); + Assert.Contains("requestInfo.configure", result); + Assert.Contains("setContentFromScalar", result); + Assert.Contains("return requestInfo;", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyForParsable() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(true); + method.AcceptedResponseTypes.Add("application/json"); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters", result); + Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); + Assert.Contains("requestInfo.configure", result); + Assert.Contains("setContentFromParsable", result); + Assert.Contains("return requestInfo;", result); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(true); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new TypeScriptConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(true); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new TypeScriptConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestExecutorBody() + { + method.Kind = CodeMethodKind.RequestExecutor; + method.HttpMethod = HttpMethod.Get; + var error4XX = root.AddClass(new CodeClass + { + Name = "Error4XX", + }).First(); + var error5XX = root.AddClass(new CodeClass + { + Name = "Error5XX", + }).First(); + var error403 = root.AddClass(new CodeClass + { + Name = "Error403", + }).First(); + method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }); + method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }); + method.AddErrorMapping("403", new CodeType { Name = "Error403", TypeDefinition = error403 }); + AddRequestBodyParameters(); + Assert.Throws(() => writer.Write(method)); + } + [Fact] + public void WritesIndexer() + { + AddRequestProperties(); + method.Kind = CodeMethodKind.IndexerBackwardCompatibility; + method.OriginalIndexer = new() + { + Name = "indx", + ReturnType = new CodeType + { + Name = "string", + }, + IndexParameter = new() + { + Name = "id", + SerializationName = "id", + Type = new CodeType + { + Name = "string", + IsNullable = true, + }, + } + }; + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("this.requestAdapter", result); + Assert.Contains("this.pathParameters", result); + Assert.Contains("id", result); + Assert.Contains("return new", result); + } + [Fact] + public void WritesPathParameterRequestBuilder() + { + AddRequestProperties(); + method.Kind = CodeMethodKind.RequestBuilderWithParameters; + method.AddParameter(new CodeParameter + { + Name = "pathParam", + Kind = CodeParameterKind.Path, + Type = new CodeType + { + Name = "string" + } + }); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + parentClass.GetImmediateParentOfType().AddConstant(constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("this.requestAdapter", result); + Assert.Contains("this.pathParameters", result); + Assert.Contains("pathParam", result); + Assert.Contains("return new", result); + } + private void AddRequestProperties() + { + parentClass.AddProperty(new CodeProperty + { + Name = "requestAdapter", + Kind = CodePropertyKind.RequestAdapter, + Type = new CodeType + { + Name = "RequestAdapter", + }, + }); + parentClass.AddProperty(new CodeProperty + { + Name = "pathParameters", + Kind = CodePropertyKind.PathParameters, + Type = new CodeType + { + Name = "string" + }, + }); + parentClass.AddProperty(new CodeProperty + { + Name = "urlTemplate", + Kind = CodePropertyKind.UrlTemplate, + Type = new CodeType + { + Name = "string" + }, + }); + } + private void AddRequestBodyParameters(bool useComplexTypeForBody = false) + { + var stringType = new CodeType + { + Name = "string", + }; + var requestConfigClass = parentClass.AddInnerClass(new CodeClass + { + Name = "RequestConfig", + Kind = CodeClassKind.RequestConfiguration, + }).First(); + requestConfigClass.AddProperty(new() + { + Name = "h", + Kind = CodePropertyKind.Headers, + Type = stringType, + }, + new() + { + Name = "q", + Kind = CodePropertyKind.QueryParameters, + Type = stringType, + }, + new() + { + Name = "o", + Kind = CodePropertyKind.Options, + Type = stringType, + }); + method.AddParameter(new CodeParameter + { + Name = "b", + Kind = CodeParameterKind.RequestBody, + Type = useComplexTypeForBody ? new CodeType + { + Name = "SomeComplexTypeForRequestBody", + TypeDefinition = TestHelper.CreateModelClass(root, "SomeComplexTypeForRequestBody"), + } : stringType, + }); + method.AddParameter(new CodeParameter + { + Name = "c", + Kind = CodeParameterKind.RequestConfiguration, + Type = new CodeType + { + Name = "RequestConfig", + TypeDefinition = requestConfigClass, + }, + Optional = true, + }); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 79f1c1c34e..2c828882ff 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -446,4 +446,180 @@ public async Task WritesMessageOverrideOnPrimary() // Then Assert.Contains("oDataError.message = oDataError.prop1 ?? \"\"", result); } + [Fact] + public void WritesApiConstructor() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.ClientConstructor; + method.IsAsync = false; + method.BaseUrl = "https://graph.microsoft.com/v1.0"; + parentClass.AddProperty(new CodeProperty + { + Name = "pathParameters", + Kind = CodePropertyKind.PathParameters, + Type = new CodeType + { + Name = "Dictionary", + IsExternal = true, + } + }); + var coreProp = parentClass.AddProperty(new CodeProperty + { + Name = "core", + Kind = CodePropertyKind.RequestAdapter, + Type = new CodeType + { + Name = "HttpCore", + IsExternal = true, + } + }).First(); + method.AddParameter(new CodeParameter + { + Name = "core", + Kind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + method.DeserializerModules = new() { "com.microsoft.kiota.serialization.Deserializer" }; + method.SerializerModules = new() { "com.microsoft.kiota.serialization.Serializer" }; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("constructor", result); + Assert.Contains("registerDefaultSerializer", result); + Assert.Contains("registerDefaultDeserializer", result); + Assert.Contains($"[\"baseurl\"] = core.baseUrl", result); + Assert.Contains($"baseUrl = \"{method.BaseUrl}\"", result); + } + [Fact] + public void WritesApiConstructorWithBackingStore() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.ClientConstructor; + var coreProp = parentClass.AddProperty(new CodeProperty + { + Name = "core", + Kind = CodePropertyKind.RequestAdapter, + Type = new CodeType + { + Name = "HttpCore", + IsExternal = true, + } + }).First(); + method.AddParameter(new CodeParameter + { + Name = "core", + Kind = CodeParameterKind.RequestAdapter, + Type = coreProp.Type, + }); + var backingStoreParam = new CodeParameter + { + Name = "backingStore", + Kind = CodeParameterKind.BackingStore, + Type = new CodeType + { + Name = "BackingStore", + IsExternal = true, + } + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); + tempWriter.SetTextWriter(tw); + tempWriter.Write(method); + var result = tw.ToString(); + Assert.Contains("enableBackingStore", result); + } + [Fact] + public void WritesDefaultValuesInFactory() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Constructor; + method.IsAsync = false; + var defaultValue = "someVal"; + var propName = "propWithDefaultValue"; + parentClass.Kind = CodeClassKind.RequestBuilder; + parentClass.AddProperty(new CodeProperty + { + Name = propName, + DefaultValue = defaultValue, + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + }, + }); + method.AddParameter(new CodeParameter + { + Name = "pathParameters", + Kind = CodeParameterKind.PathParameters, + Type = new CodeType + { + Name = "Map" + } + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains($"this.{propName} = {defaultValue}", result); + } + [Fact] + public void DoesNotWriteConstructorWithDefaultFromComposedType() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Constructor; + var defaultValue = "\"Test Value\""; + var propName = "size"; + var unionTypeWrapper = root.AddClass(new CodeClass + { + Name = "UnionTypeWrapper", + Kind = CodeClassKind.Model, + OriginalComposedType = new CodeUnionType + { + Name = "UnionTypeWrapper", + }, + DiscriminatorInformation = new() + { + DiscriminatorPropertyName = "@odata.type", + }, + }).First(); + parentClass.AddProperty(new CodeProperty + { + Name = propName, + DefaultValue = defaultValue, + Kind = CodePropertyKind.Custom, + Type = new CodeType { TypeDefinition = unionTypeWrapper } + }); + var sType = new CodeType + { + Name = "string", + }; + var arrayType = new CodeType + { + Name = "array", + }; + unionTypeWrapper.OriginalComposedType.AddType(sType); + unionTypeWrapper.OriginalComposedType.AddType(arrayType); + + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("constructor", result); + Assert.DoesNotContain(defaultValue, result);//ensure the composed type is not referenced + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 281865894a..8076b40310 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -2,11 +2,8 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Threading.Tasks; using Kiota.Builder.CodeDOM; -using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; -using Kiota.Builder.Refiners; using Kiota.Builder.Writers; using Kiota.Builder.Writers.TypeScript; @@ -54,88 +51,6 @@ public void Dispose() tw?.Dispose(); GC.SuppressFinalize(this); } - private void AddRequestProperties() - { - parentClass.AddProperty(new CodeProperty - { - Name = "requestAdapter", - Kind = CodePropertyKind.RequestAdapter, - Type = new CodeType - { - Name = "RequestAdapter", - }, - }); - parentClass.AddProperty(new CodeProperty - { - Name = "pathParameters", - Kind = CodePropertyKind.PathParameters, - Type = new CodeType - { - Name = "string" - }, - }); - parentClass.AddProperty(new CodeProperty - { - Name = "urlTemplate", - Kind = CodePropertyKind.UrlTemplate, - Type = new CodeType - { - Name = "string" - }, - }); - } - - private void AddRequestBodyParameters(bool useComplexTypeForBody = false) - { - var stringType = new CodeType - { - Name = "string", - }; - var requestConfigClass = parentClass.AddInnerClass(new CodeClass - { - Name = "RequestConfig", - Kind = CodeClassKind.RequestConfiguration, - }).First(); - requestConfigClass.AddProperty(new() - { - Name = "h", - Kind = CodePropertyKind.Headers, - Type = stringType, - }, - new() - { - Name = "q", - Kind = CodePropertyKind.QueryParameters, - Type = stringType, - }, - new() - { - Name = "o", - Kind = CodePropertyKind.Options, - Type = stringType, - }); - method.AddParameter(new CodeParameter - { - Name = "b", - Kind = CodeParameterKind.RequestBody, - Type = useComplexTypeForBody ? new CodeType - { - Name = "SomeComplexTypeForRequestBody", - TypeDefinition = TestHelper.CreateModelClass(root, "SomeComplexTypeForRequestBody"), - } : stringType, - }); - method.AddParameter(new CodeParameter - { - Name = "c", - Kind = CodeParameterKind.RequestConfiguration, - Type = new CodeType - { - Name = "RequestConfig", - TypeDefinition = requestConfigClass, - }, - Optional = true, - }); - } [Fact] public void WritesRequestBuilder() { @@ -151,29 +66,6 @@ public void WritesRequestBodiesThrowOnNullHttpMethod() Assert.Throws(() => writer.Write(method)); } [Fact] - public void WritesRequestExecutorBody() - { - method.Kind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - var error4XX = root.AddClass(new CodeClass - { - Name = "Error4XX", - }).First(); - var error5XX = root.AddClass(new CodeClass - { - Name = "Error5XX", - }).First(); - var error403 = root.AddClass(new CodeClass - { - Name = "Error403", - }).First(); - method.AddErrorMapping("4XX", new CodeType { Name = "Error4XX", TypeDefinition = error4XX }); - method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }); - method.AddErrorMapping("403", new CodeType { Name = "Error403", TypeDefinition = error403 }); - AddRequestBodyParameters(); - Assert.Throws(() => writer.Write(method)); - } - [Fact] public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() { var parentModel = TestHelper.CreateModelClass(root, "parentModel"); @@ -216,135 +108,6 @@ public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() }); Assert.Throws(() => writer.Write(factoryMethod)); } - [Fact] - public void DoesntCreateDictionaryOnEmptyErrorMapping() - {//TODO move to constants - method.Kind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("const errorMapping: Record Parsable> =", result); - Assert.Contains("undefined", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBodyForMultipart() - {// TODO move to constants - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Post; - AddRequestProperties(); - AddRequestBodyParameters(); - method.Parameters.First(static x => x.IsOfKind(CodeParameterKind.RequestBody)).Type = new CodeType { Name = "MultipartBody", IsExternal = true }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("setContentFromParsable", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestExecutorBodyForCollections() - {//TODO move to constants - method.Kind = CodeMethodKind.RequestExecutor; - method.HttpMethod = HttpMethod.Get; - method.ReturnType.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; - AddRequestBodyParameters(); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("sendCollectionAsync", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBodyForScalar() - {//TODO move to constants - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(); - method.AcceptedResponseTypes.Add("application/json"); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters)", result); - Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); - Assert.Contains("requestInfo.configure", result); - Assert.Contains("setContentFromScalar", result); - Assert.Contains("return requestInfo;", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public async Task WritesRequestGeneratorBodyForParsable() - { - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(true); - method.AcceptedResponseTypes.Add("application/json"); - await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters", result); - Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); - Assert.Contains("requestInfo.configure", result); - Assert.Contains("setContentFromParsable", result); - Assert.Contains("return requestInfo;", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBodyKnownRequestBodyType() - { - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(true); - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Post; - AddRequestProperties(); - AddRequestBodyParameters(false); - method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType - { - Name = new TypeScriptConventionService().StreamTypeName, - IsExternal = true, - }; - method.RequestBodyContentType = "application/json"; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); - Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesRequestGeneratorBodyUnknownRequestBodyType() - { - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(true); - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Post; - AddRequestProperties(); - AddRequestBodyParameters(false); - method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType - { - Name = new TypeScriptConventionService().StreamTypeName, - IsExternal = true, - }; - method.AddParameter(new CodeParameter - { - Name = "requestContentType", - Type = new CodeType() - { - Name = "string", - IsExternal = true, - }, - Kind = CodeParameterKind.RequestBodyContentType, - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); - Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); - Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] public void WritesMethodAsyncDescription() { @@ -430,18 +193,7 @@ public void Defensive() var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService(), false); Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); - var originalParent = method.Parent; - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, writer)); - method.Parent = originalParent; - } - [Fact] - public void ThrowsIfParentIsNotClass() - { - method.Parent = CodeNamespace.InitRootNamespace(); - Assert.Throws(() => writer.Write(method)); } - [Fact] public void WritesReturnType() { @@ -503,57 +255,7 @@ public void WritesProtectedMethod() Assert.Contains("protected ", result); AssertExtensions.CurlyBracesAreClosed(result); } - [Fact] - public void WritesIndexer() - { - AddRequestProperties(); - method.Kind = CodeMethodKind.IndexerBackwardCompatibility; - method.OriginalIndexer = new() - { - Name = "indx", - ReturnType = new CodeType - { - Name = "string", - }, - IndexParameter = new() - { - Name = "id", - SerializationName = "id", - Type = new CodeType - { - Name = "string", - IsNullable = true, - }, - } - }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.requestAdapter", result); - Assert.Contains("this.pathParameters", result); - Assert.Contains("id", result); - Assert.Contains("return new", result); - } - [Fact] - public void WritesPathParameterRequestBuilder() - { - AddRequestProperties(); - method.Kind = CodeMethodKind.RequestBuilderWithParameters; - method.AddParameter(new CodeParameter - { - Name = "pathParam", - Kind = CodeParameterKind.Path, - Type = new CodeType - { - Name = "string" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.requestAdapter", result); - Assert.Contains("this.pathParameters", result); - Assert.Contains("pathParam", result); - Assert.Contains("return new", result); - } + [Fact] public void WritesGetterToBackingStore() { @@ -611,38 +313,6 @@ public void WritesSetterToField() Assert.Contains("this.someProperty = value", result); } [Fact] - public void WritesConstructor() - { - method.Kind = CodeMethodKind.Constructor; - method.IsAsync = false; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.Kind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty - { - Name = propName, - DefaultValue = defaultValue, - Kind = CodePropertyKind.Custom, - Type = new CodeType - { - Name = "string", - }, - }); - AddRequestProperties(); - method.AddParameter(new CodeParameter - { - Name = "pathParameters", - Kind = CodeParameterKind.PathParameters, - Type = new CodeType - { - Name = "Map" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"this.{propName} = {defaultValue}", result); - } - [Fact] public void WritesWithUrl() { method.Kind = CodeMethodKind.RawUrlBuilder; @@ -681,127 +351,6 @@ public void WritesConstructorWithEnumValue() Assert.Contains($"this.{propName.ToFirstCharacterLowerCase()} = {codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up } [Fact] - public void DoesNotWriteConstructorWithDefaultFromComposedType() - { - method.Kind = CodeMethodKind.Constructor; - var defaultValue = "\"Test Value\""; - var propName = "size"; - var unionTypeWrapper = root.AddClass(new CodeClass - { - Name = "UnionTypeWrapper", - Kind = CodeClassKind.Model, - OriginalComposedType = new CodeUnionType - { - Name = "UnionTypeWrapper", - }, - DiscriminatorInformation = new() - { - DiscriminatorPropertyName = "@odata.type", - }, - }).First(); - parentClass.AddProperty(new CodeProperty - { - Name = propName, - DefaultValue = defaultValue, - Kind = CodePropertyKind.Custom, - Type = new CodeType { TypeDefinition = unionTypeWrapper } - }); - var sType = new CodeType - { - Name = "string", - }; - var arrayType = new CodeType - { - Name = "array", - }; - unionTypeWrapper.OriginalComposedType.AddType(sType); - unionTypeWrapper.OriginalComposedType.AddType(arrayType); - - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("constructor", result); - Assert.DoesNotContain(defaultValue, result);//ensure the composed type is not referenced - } - [Fact] - public void WritesApiConstructor() - { - method.Kind = CodeMethodKind.ClientConstructor; - method.IsAsync = false; - method.BaseUrl = "https://graph.microsoft.com/v1.0"; - parentClass.AddProperty(new CodeProperty - { - Name = "pathParameters", - Kind = CodePropertyKind.PathParameters, - Type = new CodeType - { - Name = "Dictionary", - IsExternal = true, - } - }); - var coreProp = parentClass.AddProperty(new CodeProperty - { - Name = "core", - Kind = CodePropertyKind.RequestAdapter, - Type = new CodeType - { - Name = "HttpCore", - IsExternal = true, - } - }).First(); - method.AddParameter(new CodeParameter - { - Name = "core", - Kind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - method.DeserializerModules = new() { "com.microsoft.kiota.serialization.Deserializer" }; - method.SerializerModules = new() { "com.microsoft.kiota.serialization.Serializer" }; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("constructor", result); - Assert.Contains("registerDefaultSerializer", result); - Assert.Contains("registerDefaultDeserializer", result); - Assert.Contains($"[\"baseurl\"] = core.baseUrl", result); - Assert.Contains($"baseUrl = \"{method.BaseUrl}\"", result); - } - [Fact] - public void WritesApiConstructorWithBackingStore() - { - method.Kind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty - { - Name = "core", - Kind = CodePropertyKind.RequestAdapter, - Type = new CodeType - { - Name = "HttpCore", - IsExternal = true, - } - }).First(); - method.AddParameter(new CodeParameter - { - Name = "core", - Kind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, - }); - var backingStoreParam = new CodeParameter - { - Name = "backingStore", - Kind = CodeParameterKind.BackingStore, - Type = new CodeType - { - Name = "BackingStore", - IsExternal = true, - } - }; - method.AddParameter(backingStoreParam); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(method); - var result = tw.ToString(); - Assert.Contains("enableBackingStore", result); - } - [Fact] public void WritesNameMapperMethod() { method.Kind = CodeMethodKind.QueryParametersMapper; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs index a50a67b85e..dab49e82b2 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs @@ -63,11 +63,7 @@ public void Dispose() public void WritesRequestBuilder() { property.Kind = CodePropertyKind.RequestBuilder; - writer.Write(property); - var result = tw.ToString(); - Assert.Contains($"return new {TypeName}", result); - Assert.Contains("this.requestAdapter", result); - Assert.Contains("this.pathParameters", result); + Assert.Throws(() => writer.Write(property)); } [Fact] public void WritesCustomProperty() From 96c48aa6110e87769418462a7b456a10a4b1ca3a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 15 Dec 2023 13:53:09 -0500 Subject: [PATCH 14/40] - removes dead code in typescript generation Signed-off-by: Vincent Biret --- src/Kiota.Builder/Writers/LanguageWriter.cs | 2 +- .../Writers/TypeScript/CodeFunctionWriter.cs | 70 ++++++- .../Writers/TypeScript/CodeMethodWriter.cs | 196 +----------------- .../Writers/TypeScript/CodePropertyWriter.cs | 18 +- .../Writers/TypeScript/TypeScriptWriter.cs | 6 +- .../TypeScript/CodeMethodWriterTests.cs | 2 +- 6 files changed, 68 insertions(+), 226 deletions(-) diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index ea8ab2066a..54ece0b814 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -179,7 +179,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri { GenerationLanguage.CSharp => new CSharpWriter(outputPath, clientNamespaceName), GenerationLanguage.Java => new JavaWriter(outputPath, clientNamespaceName), - GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName, usesBackingStore), + GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName), GenerationLanguage.Ruby => new RubyWriter(outputPath, clientNamespaceName), GenerationLanguage.PHP => new PhpWriter(outputPath, clientNamespaceName, usesBackingStore), GenerationLanguage.Python => new PythonWriter(outputPath, clientNamespaceName, usesBackingStore), diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index edda560e59..aea2bff86c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -11,11 +11,9 @@ namespace Kiota.Builder.Writers.TypeScript; public class CodeFunctionWriter : BaseElementWriter { - public CodeFunctionWriter(TypeScriptConventionService conventionService, string clientNamespaceName) : base(conventionService) + public CodeFunctionWriter(TypeScriptConventionService conventionService) : base(conventionService) { - _codeUsingWriter = new(clientNamespaceName); } - private readonly CodeUsingWriter _codeUsingWriter; private static readonly HashSet customSerializationWriters = new(StringComparer.OrdinalIgnoreCase) { "writeObjectValue", "writeCollectionOfObjectValues" }; public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter writer) @@ -88,7 +86,7 @@ private void WriteDiscriminatorFunction(CodeFunction codeElement, LanguageWriter var returnType = conventions.GetTypeString(codeElement.OriginalLocalMethod.ReturnType, codeElement); if (codeElement.OriginalMethodParentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType) - CodeMethodWriter.WriteDefensiveStatements(codeElement.OriginalLocalMethod, writer); + WriteDefensiveStatements(codeElement.OriginalLocalMethod, writer); WriteFactoryMethodBody(codeElement, returnType, writer); } @@ -104,12 +102,10 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, "if (mappingValue) {"); writer.IncreaseIndent(); - writer.WriteLine("switch (mappingValue) {"); - writer.IncreaseIndent(); + writer.StartBlock("switch (mappingValue) {"); foreach (var mappedType in codeElement.OriginalMethodParentClass.DiscriminatorInformation.DiscriminatorMappings) { - writer.WriteLine($"case \"{mappedType.Key}\":"); - writer.IncreaseIndent(); + writer.StartBlock($"case \"{mappedType.Key}\":"); writer.WriteLine($"return {getDeserializationFunction(codeElement, mappedType.Value.Name.ToFirstCharacterUpperCase())};"); writer.DecreaseIndent(); } @@ -123,8 +119,8 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private string getDeserializationFunction(CodeElement codeElement, string returnType) { - CodeNamespace codeNamespace = codeElement.GetImmediateParentOfType(); - CodeFunction parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; + var codeNamespace = codeElement.GetImmediateParentOfType(); + var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); } @@ -234,7 +230,7 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter { var keyName = !string.IsNullOrWhiteSpace(otherProp.SerializationName) ? otherProp.SerializationName.ToFirstCharacterLowerCase() : otherProp.Name.ToFirstCharacterLowerCase(); var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; - if (keyName.Equals(BackingStoreEnabledKey, StringComparison.Ordinal)) + if (keyName.Equals(BackingStoreEnabledKey, StringComparison.Ordinal)) //TODO the deserializer should set the default when it doesn't get a value from payload, see the pasta below writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); else writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)};{suffix} }},"); @@ -245,7 +241,59 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter else throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); } + // private static void PlaceHolderDefaultValuesPasta() { + // foreach (var propWithDefault in parentClass.GetPropertiesOfKind(DirectAccessProperties) + // .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) + // .OrderByDescending(static x => x.Kind) + // .ThenBy(static x => x.Name)) + // { + // writer.WriteLine($"this.{propWithDefault.NamePrefix}{propWithDefault.Name.ToFirstCharacterLowerCase()} = {propWithDefault.DefaultValue};"); + // } + // foreach (var propWithDefault in parentClass.GetPropertiesOfKind(SetterAccessProperties) + // .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) + // // do not apply the default value if the type is composed as the default value may not necessarily which type to use + // .Where(static x => x.Type is not CodeType propType || propType.TypeDefinition is not CodeClass propertyClass || propertyClass.OriginalComposedType is null) + // .OrderByDescending(static x => x.Kind) + // .ThenBy(static x => x.Name)) + // { + // var defaultValue = propWithDefault.DefaultValue; + // if (propWithDefault.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum enumDefinition) + // { + // defaultValue = $"{enumDefinition.Name.ToFirstCharacterUpperCase()}.{defaultValue.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}"; + // } + // writer.WriteLine($"this.{propWithDefault.Name.ToFirstCharacterLowerCase()} = {defaultValue};"); + // } + // if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && + // currentMethod.IsOfKind(CodeMethodKind.Constructor) && + // currentMethod.Parameters.FirstOrDefault(static x => x.IsOfKind(CodeParameterKind.PathParameters)) is CodeParameter pathParametersParam && + // parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty) + // { + // conventions.AddParametersAssignment(writer, + // pathParametersParam.Type.AllTypes.OfType().First(), + // pathParametersParam.Name.ToFirstCharacterLowerCase(), + // $"this.{pathParametersProperty.Name.ToFirstCharacterLowerCase()}", + // currentMethod.Parameters + // .Where(static x => x.IsOfKind(CodeParameterKind.Path)) + // .Select(x => (x.Type, string.IsNullOrEmpty(x.SerializationName) ? x.Name : x.SerializationName, x.Name.ToFirstCharacterLowerCase())) + // .ToArray()); + // } + // } + + private static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter writer) + { + if (codeElement.IsOfKind(CodeMethodKind.Setter)) return; + + var isRequestExecutor = codeElement.IsOfKind(CodeMethodKind.RequestExecutor); + foreach (var parameter in codeElement.Parameters + .Where(x => !x.Optional && !x.IsOfKind(CodeParameterKind.RequestAdapter, CodeParameterKind.PathParameters) && + !(isRequestExecutor && x.IsOfKind(CodeParameterKind.RequestBody))) + .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase)) + { + var parameterName = parameter.Name.ToFirstCharacterLowerCase(); + writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); + } + } private string GetDeserializationMethodName(CodeTypeBase propType, CodeFunction codeFunction) { var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 752dae2d3a..56b7f715dd 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -8,12 +8,9 @@ namespace Kiota.Builder.Writers.TypeScript; public class CodeMethodWriter : BaseElementWriter { - public CodeMethodWriter(TypeScriptConventionService conventionService, bool usesBackingStore) : base(conventionService) + public CodeMethodWriter(TypeScriptConventionService conventionService) : base(conventionService) { - _usesBackingStore = usesBackingStore; } - private readonly bool _usesBackingStore; - public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); @@ -25,195 +22,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var isVoid = "void".EqualsIgnoreCase(returnType); WriteMethodDocumentation(codeElement, writer, isVoid); WriteMethodPrototype(codeElement, writer, returnType, isVoid); - if (codeElement.Parent is CodeClass parentClass) - { - writer.IncreaseIndent(); - var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; - WriteDefensiveStatements(codeElement, writer); - switch (codeElement.Kind) - { - case CodeMethodKind.IndexerBackwardCompatibility: - throw new InvalidOperationException("IndexerBackwardCompatibility is implemented by constants in TypeScript."); - case CodeMethodKind.Deserializer: - throw new InvalidOperationException("Deserializers are implemented as functions in TypeScript"); - case CodeMethodKind.Serializer: - throw new InvalidOperationException("Serializers are implemented as functions in TypeScript"); - case CodeMethodKind.RequestGenerator: - throw new InvalidOperationException("RequestGenerator is implemented by constants in TypeScript."); - case CodeMethodKind.RequestExecutor: - throw new InvalidOperationException("RequestExecutor is implemented by constants in TypeScript."); - case CodeMethodKind.Getter: - WriteGetterBody(codeElement, writer, parentClass); //TODO double check whether we still need this - break; - case CodeMethodKind.Setter: - WriteSetterBody(codeElement, writer, parentClass); - break; - case CodeMethodKind.RawUrlBuilder: - throw new InvalidOperationException("RawUrlBuilder is implemented in the base type in TypeScript."); - case CodeMethodKind.ClientConstructor: - throw new InvalidOperationException("ClientConstructor is implemented in by a function in TypeScript."); - case CodeMethodKind.Constructor: - WriteConstructorBody(parentClass, codeElement, writer, inherits); //TODO double check whether we still need this - break; - case CodeMethodKind.RequestBuilderWithParameters: - throw new InvalidOperationException("RequestBuilderWithParameters is implemented by constants in TypeScript."); - case CodeMethodKind.QueryParametersMapper: - throw new InvalidOperationException("TypeScript relies on a constant for query parameters mapping"); - case CodeMethodKind.Factory: - throw new InvalidOperationException("Factory methods are implemented as functions in TypeScript"); - case CodeMethodKind.RawUrlConstructor: - throw new InvalidOperationException("RawUrlConstructor is not supported as typescript relies on union types."); - case CodeMethodKind.RequestBuilderBackwardCompatibility: - throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties."); - case CodeMethodKind.ErrorMessageOverride: - throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by the deserializer function in typescript."); - default: - WriteDefaultMethodBody(codeElement, writer); //TODO double check whether we still need this - break; - } - writer.CloseBlock(); //TODO if all cases are not needed anymore, drop the class - } - } - - internal static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter writer) - { - if (codeElement.IsOfKind(CodeMethodKind.Setter)) return; - - var isRequestExecutor = codeElement.IsOfKind(CodeMethodKind.RequestExecutor); - - foreach (var parameter in codeElement.Parameters - .Where(x => !x.Optional && !x.IsOfKind(CodeParameterKind.RequestAdapter, CodeParameterKind.PathParameters) && - !(isRequestExecutor && x.IsOfKind(CodeParameterKind.RequestBody))) - .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase)) - { - var parameterName = parameter.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"if(!{parameterName}) throw new Error(\"{parameterName} cannot be undefined\");"); - } - } - - private CodePropertyKind[]? _DirectAccessProperties; - private CodePropertyKind[] DirectAccessProperties - { - get - { - if (_DirectAccessProperties == null) - { - var directAccessProperties = new List { - CodePropertyKind.BackingStore, - CodePropertyKind.RequestBuilder, - CodePropertyKind.UrlTemplate, - CodePropertyKind.PathParameters - }; - if (!_usesBackingStore) - { - directAccessProperties.Add(CodePropertyKind.AdditionalData); - } - _DirectAccessProperties = directAccessProperties.ToArray(); - } - return _DirectAccessProperties; - } - } - private CodePropertyKind[]? _SetterAccessProperties; - private CodePropertyKind[] SetterAccessProperties - { - get - { - _SetterAccessProperties ??= new[] { - CodePropertyKind.AdditionalData, //additional data and custom properties need to use the accessors in case of backing store use - CodePropertyKind.Custom - }.Except(DirectAccessProperties) - .ToArray(); - return _SetterAccessProperties; - } - } - private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer, bool inherits) - { - if (inherits || parentClass.IsErrorDefinition) - if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && - currentMethod.Parameters.OfKind(CodeParameterKind.RequestAdapter) is CodeParameter requestAdapterParameter && - parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty && - !string.IsNullOrEmpty(urlTemplateProperty.DefaultValue)) - { - var pathParametersValue = "{}"; - if (currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParameter) - pathParametersValue = pathParametersParameter.Name.ToFirstCharacterLowerCase(); - writer.WriteLine($"super({pathParametersValue}, {requestAdapterParameter.Name.ToFirstCharacterLowerCase()}, {urlTemplateProperty.DefaultValue}, (x, y) => new {parentClass.Name.ToFirstCharacterUpperCase()}({(currentMethod.Kind is CodeMethodKind.Constructor ? "x, " : string.Empty)}y));"); - } - else - writer.WriteLine("super();"); - - foreach (var propWithDefault in parentClass.GetPropertiesOfKind(DirectAccessProperties) - .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) - .OrderByDescending(static x => x.Kind) - .ThenBy(static x => x.Name)) - { - writer.WriteLine($"this.{propWithDefault.NamePrefix}{propWithDefault.Name.ToFirstCharacterLowerCase()} = {propWithDefault.DefaultValue};"); - } - - foreach (var propWithDefault in parentClass.GetPropertiesOfKind(SetterAccessProperties) - .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) - // do not apply the default value if the type is composed as the default value may not necessarily which type to use - .Where(static x => x.Type is not CodeType propType || propType.TypeDefinition is not CodeClass propertyClass || propertyClass.OriginalComposedType is null) - .OrderByDescending(static x => x.Kind) - .ThenBy(static x => x.Name)) - { - var defaultValue = propWithDefault.DefaultValue; - if (propWithDefault.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum enumDefinition) - { - defaultValue = $"{enumDefinition.Name.ToFirstCharacterUpperCase()}.{defaultValue.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}"; - } - writer.WriteLine($"this.{propWithDefault.Name.ToFirstCharacterLowerCase()} = {defaultValue};"); - } - if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && - currentMethod.IsOfKind(CodeMethodKind.Constructor) && - currentMethod.Parameters.FirstOrDefault(static x => x.IsOfKind(CodeParameterKind.PathParameters)) is CodeParameter pathParametersParam && - parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty) - { - conventions.AddParametersAssignment(writer, - pathParametersParam.Type.AllTypes.OfType().First(), - pathParametersParam.Name.ToFirstCharacterLowerCase(), - $"this.{pathParametersProperty.Name.ToFirstCharacterLowerCase()}", - currentMethod.Parameters - .Where(static x => x.IsOfKind(CodeParameterKind.Path)) - .Select(x => (x.Type, string.IsNullOrEmpty(x.SerializationName) ? x.Name : x.SerializationName, x.Name.ToFirstCharacterLowerCase())) - .ToArray()); - } - } - private static void WriteSetterBody(CodeMethod codeElement, LanguageWriter writer, CodeClass parentClass) - { - var backingStore = parentClass.GetBackingStoreProperty(); - if (backingStore == null) - writer.WriteLine($"this.{codeElement.AccessedProperty?.NamePrefix}{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()} = value;"); - else - writer.WriteLine($"this.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.set(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\", value);"); - } - private void WriteGetterBody(CodeMethod codeElement, LanguageWriter writer, CodeClass parentClass) - { - var backingStore = parentClass.GetBackingStoreProperty(); - if (backingStore == null) - writer.WriteLine($"return this.{codeElement.AccessedProperty?.NamePrefix}{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()};"); - else - if (!(codeElement.AccessedProperty?.Type?.IsNullable ?? true) && - !(codeElement.AccessedProperty?.ReadOnly ?? true) && - !string.IsNullOrEmpty(codeElement.AccessedProperty?.DefaultValue)) - { - writer.WriteLines($"let value = this.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.get<{conventions.GetTypeString(codeElement.AccessedProperty.Type, codeElement)}>(\"{codeElement.AccessedProperty.Name.ToFirstCharacterLowerCase()}\");", - "if(!value) {"); - writer.IncreaseIndent(); - writer.WriteLines($"value = {codeElement.AccessedProperty.DefaultValue};", - $"this.{codeElement.AccessedProperty?.NamePrefix}{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()} = value;"); - writer.DecreaseIndent(); - writer.WriteLines("}", "return value;"); - } - else - writer.WriteLine($"return this.{backingStore.NamePrefix}{backingStore.Name.ToFirstCharacterLowerCase()}.get(\"{codeElement.AccessedProperty?.Name?.ToFirstCharacterLowerCase()}\");"); - - } - private static void WriteDefaultMethodBody(CodeMethod codeElement, LanguageWriter writer) - { - var promisePrefix = codeElement.IsAsync ? "Promise.resolve(" : string.Empty; - var promiseSuffix = codeElement.IsAsync ? ")" : string.Empty; - writer.WriteLine($"return {promisePrefix}{(codeElement.ReturnType.Name.Equals("string", StringComparison.OrdinalIgnoreCase) ? "''" : "{} as any")}{promiseSuffix};"); + if (codeElement.Parent is CodeClass) + throw new InvalidOperationException("No method implementations are generated in typescript: either functions or constants."); } private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, bool isVoid) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index a1d4478563..508c90b7f3 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -23,11 +23,9 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w WriteCodePropertyForInterface(codeElement, writer, returnType, isFlagEnum); break; case CodeClass: - WriteCodePropertyForClass(codeElement, writer, returnType, isFlagEnum); - break; + throw new InvalidOperationException($"All properties are defined on interfaces in TypeScript."); } } - private static void WriteCodePropertyForInterface(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) { switch (codeElement.Kind) @@ -40,18 +38,4 @@ private static void WriteCodePropertyForInterface(CodeProperty codeElement, Lang break; } } - - private void WriteCodePropertyForClass(CodeProperty codeElement, LanguageWriter writer, string returnType, bool isFlagEnum) - {//TODO double check we still need this - switch (codeElement.Kind) - { - case CodePropertyKind.ErrorMessageOverride: - throw new InvalidOperationException($"Primary message mapping is done in deserializer function in TypeScript."); - case CodePropertyKind.RequestBuilder: - throw new InvalidOperationException($"Request builder property is implemented via a constant in TypeScript."); - default: - writer.WriteLine($"{conventions.GetAccessModifier(codeElement.Access)} {codeElement.NamePrefix}{codeElement.Name.ToFirstCharacterLowerCase()}{(codeElement.Type.IsNullable ? "?" : string.Empty)}: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); - break; - } - } } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index ea6306f2f0..33d3eb6c45 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -4,15 +4,15 @@ namespace Kiota.Builder.Writers.TypeScript; public class TypeScriptWriter : LanguageWriter { - public TypeScriptWriter(string rootPath, string clientNamespaceName, bool usesBackingStore = false) + public TypeScriptWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new TypeScriptPathSegmenter(rootPath, clientNamespaceName); var conventionService = new TypeScriptConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService, clientNamespaceName)); AddOrReplaceCodeElementWriter(new CodeBlockEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService, usesBackingStore)); - AddOrReplaceCodeElementWriter(new CodeFunctionWriter(conventionService, clientNamespaceName)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeFunctionWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 8076b40310..cdea2c0f60 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -190,7 +190,7 @@ public void WritesMethodDescriptionLink() [Fact] public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService(), false); + var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService()); Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); } From 801ec799a78c824f3ee2194fe2c1b637ecf65d70 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 12:07:46 -0500 Subject: [PATCH 15/40] - fixes typescript property and refiner tests Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodePropertyWriter.cs | 2 +- .../TypeScriptLanguageRefinerTests.cs | 10 +++++++ .../TypeScript/CodePropertyWriterTests.cs | 26 ++++++++++++++----- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs index 508c90b7f3..bd5ee91049 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodePropertyWriter.cs @@ -31,7 +31,7 @@ private static void WriteCodePropertyForInterface(CodeProperty codeElement, Lang switch (codeElement.Kind) { case CodePropertyKind.RequestBuilder: - writer.WriteLine($"get {codeElement.Name.ToFirstCharacterLowerCase()}(): {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); + writer.WriteLine($"get {codeElement.Name.ToFirstCharacterLowerCase()}(): {returnType};"); break; default: writer.WriteLine($"{codeElement.Name.ToFirstCharacterLowerCase()}?: {returnType}{(isFlagEnum ? "[]" : string.Empty)};"); diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index e432369a52..accd0ea3a6 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -636,6 +636,16 @@ public async Task ReplaceRequestConfigsQueryParams() Name = "requestBuilder", Kind = CodeClassKind.RequestBuilder }).First(); + requestBuilder.AddProperty(new CodeProperty + { + Kind = CodePropertyKind.UrlTemplate, + Name = "urlTemplate", + DefaultValue = "{baseurl+}", + Type = new CodeType + { + Name = "string" + } + }); var requestConfig = requestBuilder.AddInnerClass(new CodeClass { diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs index dab49e82b2..00bf2a47d8 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodePropertyWriterTests.cs @@ -1,6 +1,6 @@ using System; using System.IO; - +using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Writers; @@ -14,7 +14,7 @@ public sealed class CodePropertyWriterTests : IDisposable private readonly StringWriter tw; private readonly LanguageWriter writer; private readonly CodeProperty property; - private readonly CodeClass parentClass; + private readonly CodeInterface parentInterface; private const string PropertyName = "propertyName"; private const string TypeName = "Somecustomtype"; public CodePropertyWriterTests() @@ -23,11 +23,10 @@ public CodePropertyWriterTests() tw = new StringWriter(); writer.SetTextWriter(tw); var root = CodeNamespace.InitRootNamespace(); - parentClass = new CodeClass + parentInterface = root.AddInterface(new CodeInterface { Name = "parentClass" - }; - root.AddClass(parentClass); + }).First(); property = new CodeProperty { Name = PropertyName, @@ -36,7 +35,7 @@ public CodePropertyWriterTests() Name = TypeName } }; - parentClass.AddProperty(property, new() + parentInterface.AddProperty(property, new() { Name = "pathParameters", Kind = CodePropertyKind.PathParameters, @@ -63,7 +62,10 @@ public void Dispose() public void WritesRequestBuilder() { property.Kind = CodePropertyKind.RequestBuilder; - Assert.Throws(() => writer.Write(property)); + writer.Write(property); + var result = tw.ToString(); + Assert.Contains("get", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("?", result, StringComparison.OrdinalIgnoreCase); } [Fact] public void WritesCustomProperty() @@ -110,4 +112,14 @@ public void WritesCollectionFlagEnumsAsOneDimensionalArray() Assert.Contains("[]", result); Assert.DoesNotContain("[] []", result); } + [Fact] + public void FailsOnPropertiesForClasses() + { + property.Kind = CodePropertyKind.Custom; + property.Parent = new CodeClass + { + Name = "parentClass" + }; + Assert.Throws(() => writer.Write(property)); + } } From b6a063e3d966f8d9918a3a833fe72c715f4e0659 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 12:29:25 -0500 Subject: [PATCH 16/40] - code linting Signed-off-by: Vincent Biret --- src/Kiota.Builder/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs index 8266834bcd..46a9258f6c 100644 --- a/src/Kiota.Builder/Extensions/StringExtensions.cs +++ b/src/Kiota.Builder/Extensions/StringExtensions.cs @@ -280,5 +280,5 @@ public static string CleanupXMLString(this string? original) /// The second string /// public static bool EqualsIgnoreCase(this string? a, string? b) - => String.Equals(a, b, StringComparison.OrdinalIgnoreCase); + => string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } From 4ae477f50d7709f703b7d882905cd0d8ddb6d00d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 12:29:50 -0500 Subject: [PATCH 17/40] - fixes missing documentation and deprecation information on functions Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeFunctionWriter.cs | 5 +- .../Writers/TypeScript/CodeMethodWriter.cs | 6 +- .../TypeScript/CodeFunctionWriterTests.cs | 23 +++++ .../TypeScript/CodeMethodWriterTests.cs | 87 +++---------------- 4 files changed, 42 insertions(+), 79 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index aea2bff86c..1de184c8e4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -27,8 +27,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w var codeMethod = codeElement.OriginalLocalMethod; var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty; - //TODO documentation is missing - CodeMethodWriter.WriteMethodPrototypeInternal(codeElement.OriginalLocalMethod, writer, returnType, false, conventions, true); + var isVoid = "void".EqualsIgnoreCase(returnType); + CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.OriginalLocalMethod, writer, isVoid, conventions); + CodeMethodWriter.WriteMethodPrototypeInternal(codeElement.OriginalLocalMethod, writer, returnType, isVoid, conventions, true); writer.IncreaseIndent(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 56b7f715dd..2039c9db91 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -27,6 +27,10 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri } private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, bool isVoid) + { + WriteMethodDocumentationInternal(code, writer, isVoid, conventions); + } + internal static void WriteMethodDocumentationInternal(CodeMethod code, LanguageWriter writer, bool isVoid, TypeScriptConventionService typeScriptConventionService) { var returnRemark = (isVoid, code.IsAsync) switch { @@ -34,7 +38,7 @@ private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, bo (false, true) => $"@returns a Promise of {code.ReturnType.Name.ToFirstCharacterUpperCase()}", (false, false) => $"@returns a {code.ReturnType.Name}", }; - conventions.WriteLongDescription(code, + typeScriptConventionService.WriteLongDescription(code, writer, code.Parameters .Where(static x => x.Documentation.DescriptionAvailable) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 2c828882ff..1f90359bb1 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -622,4 +623,26 @@ public void DoesNotWriteConstructorWithDefaultFromComposedType() Assert.Contains("constructor", result); Assert.DoesNotContain(defaultValue, result);//ensure the composed type is not referenced } + [Fact] + public void WritesDeprecationInformation() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Factory; + method.IsStatic = true; + method.Deprecation = new("This method is deprecated", DateTimeOffset.Parse("2020-01-01T00:00:00Z", CultureInfo.InvariantCulture), DateTimeOffset.Parse("2021-01-01T00:00:00Z", CultureInfo.InvariantCulture), "v2.0"); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.Contains("This method is deprecated", result); + Assert.Contains("2020-01-01", result); + Assert.Contains("2021-01-01", result); + Assert.Contains("v2.0", result); + Assert.Contains("@deprecated", result); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index cdea2c0f60..ebd72b49ec 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.IO; using System.Linq; using Kiota.Builder.CodeDOM; @@ -110,8 +109,7 @@ public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() } [Fact] public void WritesMethodAsyncDescription() - { - + {//TODO move to functions method.Documentation.Description = MethodDescription; var parameter = new CodeParameter { @@ -140,8 +138,7 @@ public void WritesMethodAsyncDescription() [Fact] public void WritesMethodSyncDescription() - { - + {//TODO move to functions method.Documentation.Description = MethodDescription; method.IsAsync = false; var parameter = new CodeParameter @@ -164,7 +161,7 @@ public void WritesMethodSyncDescription() } [Fact] public void WritesMethodDescriptionLink() - { + {//TODO move to functions method.Documentation.Description = MethodDescription; method.Documentation.DocumentationLabel = "see more"; method.Documentation.DocumentationLink = new("https://foo.org/docs"); @@ -196,7 +193,7 @@ public void Defensive() } [Fact] public void WritesReturnType() - { + { //TODO move to functions writer.Write(method); var result = tw.ToString(); Assert.Contains(MethodName, result); @@ -208,7 +205,7 @@ public void WritesReturnType() [Fact] public void DoesNotAddUndefinedOnNonNullableReturnType() - { + { //TODO move to functions method.ReturnType.IsNullable = false; writer.Write(method); var result = tw.ToString(); @@ -218,7 +215,7 @@ public void DoesNotAddUndefinedOnNonNullableReturnType() [Fact] public void DoesNotAddAsyncInformationOnSyncMethods() - { + { //TODO move to functions method.IsAsync = false; writer.Write(method); var result = tw.ToString(); @@ -229,7 +226,7 @@ public void DoesNotAddAsyncInformationOnSyncMethods() [Fact] public void WritesPublicMethodByDefault() - { + { //TODO move to functions writer.Write(method); var result = tw.ToString(); Assert.Contains("public ", result);// public default @@ -238,7 +235,7 @@ public void WritesPublicMethodByDefault() [Fact] public void WritesPrivateMethod() - { + { //TODO move to functions method.Access = AccessModifier.Private; writer.Write(method); var result = tw.ToString(); @@ -255,62 +252,12 @@ public void WritesProtectedMethod() Assert.Contains("protected ", result); AssertExtensions.CurlyBracesAreClosed(result); } - - [Fact] - public void WritesGetterToBackingStore() - { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.Kind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.backingStore.get(\"someProperty\")", result); - } - [Fact] - public void WritesGetterToBackingStoreWithNonnullProperty() - { - method.AddAccessedProperty(); - parentClass.AddBackingStoreProperty(); - method.AccessedProperty.Type = new CodeType - { - Name = "string", - IsNullable = false, - }; - var defaultValue = "someDefaultValue"; - method.AccessedProperty.DefaultValue = defaultValue; - method.Kind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("if(!value)", result); - Assert.Contains(defaultValue, result); - } - [Fact] - public void WritesSetterToBackingStore() - { - parentClass.AddBackingStoreProperty(); - method.AddAccessedProperty(); - method.Kind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.backingStore.set(\"someProperty\", value)", result); - } [Fact] - public void WritesGetterToField() + public void FailsOnCodeClassParent() { method.AddAccessedProperty(); method.Kind = CodeMethodKind.Getter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.someProperty", result); - } - [Fact] - public void WritesSetterToField() - { - method.AddAccessedProperty(); - method.Kind = CodeMethodKind.Setter; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("this.someProperty = value", result); + Assert.Throws(() => writer.Write(method)); } [Fact] public void WritesWithUrl() @@ -331,7 +278,7 @@ public void WritesWithUrl() } [Fact] public void WritesConstructorWithEnumValue() - { + {//TODO move to functions method.Kind = CodeMethodKind.Constructor; var defaultValue = "1024x1024"; var propName = "size"; @@ -396,16 +343,4 @@ public void WritesNameMapperMethod() }); Assert.Throws(() => writer.Write(method)); } - [Fact] - public void WritesDeprecationInformation() - { - method.Deprecation = new("This method is deprecated", DateTimeOffset.Parse("2020-01-01T00:00:00Z", CultureInfo.InvariantCulture), DateTimeOffset.Parse("2021-01-01T00:00:00Z", CultureInfo.InvariantCulture), "v2.0"); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("This method is deprecated", result); - Assert.Contains("2020-01-01", result); - Assert.Contains("2021-01-01", result); - Assert.Contains("v2.0", result); - Assert.Contains("@deprecated", result); - } } From b087ec82fe558e74b5739ba704a7c393c1065062 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 12:45:59 -0500 Subject: [PATCH 18/40] - moves async documentation tests to function Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeMethodWriter.cs | 5 +- .../TypeScript/CodeFunctionWriterTests.cs | 110 ++++++++++++++++++ .../TypeScript/CodeMethodWriterTests.cs | 80 ------------- 3 files changed, 112 insertions(+), 83 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 2039c9db91..8a5f624097 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; @@ -62,7 +61,7 @@ internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWrite })?.ToFirstCharacterLowerCase(); var asyncPrefix = code.IsAsync && code.Kind != CodeMethodKind.RequestExecutor ? " async " : string.Empty; var staticPrefix = code.IsStatic && !isFunction ? "static " : string.Empty; - var functionPrefix = isFunction ? "export function " : " "; + var functionPrefix = isFunction ? $"export{asyncPrefix.TrimEnd()} function " : " "; var parameters = string.Join(", ", code.Parameters.Order(parameterOrderComparer).Select(p => pConventions.GetParameterSignature(p, code))); var asyncReturnTypePrefix = code.IsAsync ? "Promise<" : string.Empty; var asyncReturnTypeSuffix = code.IsAsync ? ">" : string.Empty; @@ -76,6 +75,6 @@ internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWrite var shouldHaveTypeSuffix = !code.IsAccessor && !isConstructor && !string.IsNullOrEmpty(returnType); var returnTypeSuffix = shouldHaveTypeSuffix ? $" : {asyncReturnTypePrefix}{returnType}{nullableSuffix}{asyncReturnTypeSuffix}" : string.Empty; var openBracketSuffix = code.Parent is CodeClass || isFunction ? " {" : ";"; - writer.WriteLine($"{accessModifier}{functionPrefix}{accessorPrefix}{staticPrefix}{methodName}{asyncPrefix}({parameters}){returnTypeSuffix}{openBracketSuffix}"); + writer.WriteLine($"{accessModifier}{functionPrefix}{accessorPrefix}{staticPrefix}{methodName}{(isFunction ? string.Empty : asyncPrefix)}({parameters}){returnTypeSuffix}{openBracketSuffix}"); } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 1f90359bb1..2b8c3ee3b1 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -645,4 +645,114 @@ public void WritesDeprecationInformation() Assert.Contains("v2.0", result); Assert.Contains("@deprecated", result); } + private const string MethodDescription = "some description"; + private const string ParamDescription = "some parameter description"; + private const string ParamName = "paramName"; + [Fact] + public void WritesMethodAsyncDescription() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Factory; + method.IsStatic = true; + method.Documentation.Description = MethodDescription; + var parameter = new CodeParameter + { + Documentation = new() + { + Description = ParamDescription, + }, + Name = ParamName, + Type = new CodeType + { + Name = "string" + } + }; + method.AddParameter(parameter); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.Contains("/**", result); + Assert.Contains(MethodDescription, result); + Assert.Contains("@param ", result); + Assert.Contains(ParamName, result); + Assert.Contains(ParamDescription, result); + Assert.Contains("@returns a Promise of", result); + Assert.Contains("*/", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public void WritesMethodSyncDescription() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Factory; + method.IsStatic = true; + method.Documentation.Description = MethodDescription; + method.IsAsync = false; + var parameter = new CodeParameter + { + Documentation = new() + { + Description = ParamDescription, + }, + Name = ParamName, + Type = new CodeType + { + Name = "string" + } + }; + method.AddParameter(parameter); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.DoesNotContain("@returns a Promise of", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + [Fact] + public void WritesMethodDescriptionLink() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Factory; + method.IsStatic = true; + method.Documentation.Description = MethodDescription; + method.Documentation.DocumentationLabel = "see more"; + method.Documentation.DocumentationLink = new("https://foo.org/docs"); + method.IsAsync = false; + var parameter = new CodeParameter + { + Documentation = new() + { + Description = ParamDescription, + }, + Name = ParamName, + Type = new CodeType + { + Name = "string" + } + }; + method.AddParameter(parameter); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.Contains("@see {@link", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index ebd72b49ec..0582e4e190 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -20,9 +20,6 @@ public sealed class CodeMethodWriterTests : IDisposable private readonly CodeNamespace root; private const string MethodName = "methodName"; private const string ReturnTypeName = "Somecustomtype"; - private const string MethodDescription = "some description"; - private const string ParamDescription = "some parameter description"; - private const string ParamName = "paramName"; public CodeMethodWriterTests() { writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); @@ -108,83 +105,6 @@ public void WritesModelFactoryBodyThrowsIfMethodAndNotFactory() Assert.Throws(() => writer.Write(factoryMethod)); } [Fact] - public void WritesMethodAsyncDescription() - {//TODO move to functions - method.Documentation.Description = MethodDescription; - var parameter = new CodeParameter - { - Documentation = new() - { - Description = ParamDescription, - }, - Name = ParamName, - Type = new CodeType - { - Name = "string" - } - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("/**", result); - Assert.Contains(MethodDescription, result); - Assert.Contains("@param ", result); - Assert.Contains(ParamName, result); - Assert.Contains(ParamDescription, result); - Assert.Contains("@returns a Promise of", result); - Assert.Contains("*/", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void WritesMethodSyncDescription() - {//TODO move to functions - method.Documentation.Description = MethodDescription; - method.IsAsync = false; - var parameter = new CodeParameter - { - Documentation = new() - { - Description = ParamDescription, - }, - Name = ParamName, - Type = new CodeType - { - Name = "string" - } - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("@returns a Promise of", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] - public void WritesMethodDescriptionLink() - {//TODO move to functions - method.Documentation.Description = MethodDescription; - method.Documentation.DocumentationLabel = "see more"; - method.Documentation.DocumentationLink = new("https://foo.org/docs"); - method.IsAsync = false; - var parameter = new CodeParameter - { - Documentation = new() - { - Description = ParamDescription, - }, - Name = ParamName, - Type = new CodeType - { - Name = "string" - } - }; - method.AddParameter(parameter); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("@see {@link", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] public void Defensive() { var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService()); From 1c44bbae4ed2b53b20f1200ffb3648c51b7e8e63 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 13:15:57 -0500 Subject: [PATCH 19/40] - code linting Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeFunctionWriter.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 1de184c8e4..5b885afa48 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -128,8 +128,13 @@ private string getDeserializationFunction(CodeElement codeElement, string return private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer) { - var param = codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(static x => x.Type is CodeType type && type.TypeDefinition is CodeInterface); - if (param == null || param.Type is not CodeType codeType || codeType.TypeDefinition is not CodeInterface codeInterface) + if (codeElement.OriginalLocalMethod.Parameters.FirstOrDefault(static x => x.Type is CodeType type && type.TypeDefinition is CodeInterface) is not + { + Type: CodeType + { + TypeDefinition: CodeInterface codeInterface + } + } param) throw new InvalidOperationException("Interface parameter not found for code interface"); if (codeInterface.StartBlock.Implements.FirstOrDefault(static x => x.TypeDefinition is CodeInterface) is CodeType inherits) From 46d313344f4e2fbc5195f75ec1c7649fc33a6a66 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 13:16:41 -0500 Subject: [PATCH 20/40] - moves return type test to functions - moves access modifier tests to functions Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeMethodWriter.cs | 3 +- .../TypeScript/CodeFunctionWriterTests.cs | 198 ++++++++++++++++++ .../TypeScript/CodeMethodWriterTests.cs | 61 ------ 3 files changed, 200 insertions(+), 62 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 8a5f624097..f90f287497 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -61,7 +61,8 @@ internal static void WriteMethodPrototypeInternal(CodeMethod code, LanguageWrite })?.ToFirstCharacterLowerCase(); var asyncPrefix = code.IsAsync && code.Kind != CodeMethodKind.RequestExecutor ? " async " : string.Empty; var staticPrefix = code.IsStatic && !isFunction ? "static " : string.Empty; - var functionPrefix = isFunction ? $"export{asyncPrefix.TrimEnd()} function " : " "; + var exportPrefix = code.Access is AccessModifier.Public ? "export" : string.Empty; + var functionPrefix = isFunction ? $"{exportPrefix}{asyncPrefix.TrimEnd()} function " : " "; var parameters = string.Join(", ", code.Parameters.Order(parameterOrderComparer).Select(p => pConventions.GetParameterSignature(p, code))); var asyncReturnTypePrefix = code.IsAsync ? "Promise<" : string.Empty; var asyncReturnTypeSuffix = code.IsAsync ? ">" : string.Empty; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 2b8c3ee3b1..34bf4691c9 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -755,4 +755,202 @@ public void WritesMethodDescriptionLink() Assert.Contains("@see {@link", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } + [Fact] + public void WritesReturnType() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.Contains(MethodName, result); + Assert.Contains(ReturnTypeName, result); + Assert.Contains("Promise<", result);// async default + Assert.Contains("| undefined", result);// nullable default + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + [Fact] + public void DoesNotAddUndefinedOnNonNullableReturnType() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + method.ReturnType.IsNullable = false; + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.DoesNotContain("| undefined", result.Substring(result.IndexOf("Promise<", StringComparison.OrdinalIgnoreCase))); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public void DoesNotAddAsyncInformationOnSyncMethods() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + method.IsAsync = false; + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.DoesNotContain("Promise<", result); + Assert.DoesNotContain("async", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public void WritesPublicMethodByDefault() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.Contains("export ", result);// public default + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public void WritesPrivateMethod() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.Access = AccessModifier.Private; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.DoesNotContain("export ", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + + [Fact] + public void WritesProtectedMethod() + { + var parentClass = root.AddClass(new CodeClass + { + Name = "ODataError", + Kind = CodeClassKind.Model, + }).First(); + var targetInterface = root.AddInterface(new CodeInterface + { + Name = "SomeInterface", + Kind = CodeInterfaceKind.Model, + }).First(); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsStatic = true; + method.Access = AccessModifier.Protected; + method.AddParameter(new CodeParameter + { + Name = "someParam", + Type = new CodeType + { + TypeDefinition = targetInterface, + } + }); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function); + writer.Write(function); + var result = tw.ToString(); + Assert.DoesNotContain("export ", result); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 0582e4e190..e9e141bf1a 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -112,67 +112,6 @@ public void Defensive() Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); } [Fact] - public void WritesReturnType() - { //TODO move to functions - writer.Write(method); - var result = tw.ToString(); - Assert.Contains(MethodName, result); - Assert.Contains(ReturnTypeName, result); - Assert.Contains("Promise<", result);// async default - Assert.Contains("| undefined", result);// nullable default - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void DoesNotAddUndefinedOnNonNullableReturnType() - { //TODO move to functions - method.ReturnType.IsNullable = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("| undefined", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void DoesNotAddAsyncInformationOnSyncMethods() - { //TODO move to functions - method.IsAsync = false; - writer.Write(method); - var result = tw.ToString(); - Assert.DoesNotContain("Promise<", result); - Assert.DoesNotContain("async", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void WritesPublicMethodByDefault() - { //TODO move to functions - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("public ", result);// public default - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void WritesPrivateMethod() - { //TODO move to functions - method.Access = AccessModifier.Private; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("private ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - - [Fact] - public void WritesProtectedMethod() - { - method.Access = AccessModifier.Protected; - writer.Write(method); - var result = tw.ToString(); - Assert.Contains("protected ", result); - AssertExtensions.CurlyBracesAreClosed(result); - } - [Fact] public void FailsOnCodeClassParent() { method.AddAccessedProperty(); From c7d3a8567a55636552692ee91b28dfcfae7903f0 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 13:57:43 -0500 Subject: [PATCH 21/40] - restores default values functionality in typescript Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeFunctionWriter.cs | 57 +++++-------------- .../TypeScript/CodeFunctionWriterTests.cs | 30 ++++++++++ .../TypeScript/CodeMethodWriterTests.cs | 22 ------- 3 files changed, 45 insertions(+), 64 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 5b885afa48..0f93b86e8b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -164,11 +164,12 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro var propTypeName = conventions.GetTypeString(codeProperty.Type, codeProperty.Parent!, false); var serializationName = GetSerializationMethodName(codeProperty.Type); + var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; 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}, {serializeName});"); + writer.WriteLine($"writer.{serializationName}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix}, {serializeName});"); } else { @@ -176,7 +177,7 @@ private void WritePropertySerializer(string modelParamName, CodeProperty codePro { writer.WriteLine($"if({modelParamName}.{codePropertyName})"); } - writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName});"); + writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix});"); } } @@ -236,10 +237,13 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter { var keyName = !string.IsNullOrWhiteSpace(otherProp.SerializationName) ? otherProp.SerializationName.ToFirstCharacterLowerCase() : otherProp.Name.ToFirstCharacterLowerCase(); var suffix = otherProp.Name.Equals(primaryErrorMappingKey, StringComparison.Ordinal) ? primaryErrorMapping : string.Empty; - if (keyName.Equals(BackingStoreEnabledKey, StringComparison.Ordinal)) //TODO the deserializer should set the default when it doesn't get a value from payload, see the pasta below + if (keyName.Equals(BackingStoreEnabledKey, StringComparison.Ordinal)) writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = true;{suffix} }},"); else - writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)};{suffix} }},"); + { + var defaultValueSuffix = GetDefaultValueLiteralForProperty(otherProp) is string dft && !string.IsNullOrEmpty(dft) ? $" ?? {dft}" : string.Empty; + writer.WriteLine($"\"{keyName}\": n => {{ {param.Name.ToFirstCharacterLowerCase()}.{otherProp.Name.ToFirstCharacterLowerCase()} = n.{GetDeserializationMethodName(otherProp.Type, codeFunction)}{defaultValueSuffix};{suffix} }},"); + } } writer.CloseBlock(); @@ -247,44 +251,13 @@ private void WriteDeserializerFunction(CodeFunction codeFunction, LanguageWriter else throw new InvalidOperationException($"Model interface for deserializer function {codeFunction.Name} is not available"); } - // private static void PlaceHolderDefaultValuesPasta() { - // foreach (var propWithDefault in parentClass.GetPropertiesOfKind(DirectAccessProperties) - // .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) - // .OrderByDescending(static x => x.Kind) - // .ThenBy(static x => x.Name)) - // { - // writer.WriteLine($"this.{propWithDefault.NamePrefix}{propWithDefault.Name.ToFirstCharacterLowerCase()} = {propWithDefault.DefaultValue};"); - // } - // foreach (var propWithDefault in parentClass.GetPropertiesOfKind(SetterAccessProperties) - // .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate)) - // // do not apply the default value if the type is composed as the default value may not necessarily which type to use - // .Where(static x => x.Type is not CodeType propType || propType.TypeDefinition is not CodeClass propertyClass || propertyClass.OriginalComposedType is null) - // .OrderByDescending(static x => x.Kind) - // .ThenBy(static x => x.Name)) - // { - // var defaultValue = propWithDefault.DefaultValue; - // if (propWithDefault.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum enumDefinition) - // { - // defaultValue = $"{enumDefinition.Name.ToFirstCharacterUpperCase()}.{defaultValue.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}"; - // } - // writer.WriteLine($"this.{propWithDefault.Name.ToFirstCharacterLowerCase()} = {defaultValue};"); - // } - // if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && - // currentMethod.IsOfKind(CodeMethodKind.Constructor) && - // currentMethod.Parameters.FirstOrDefault(static x => x.IsOfKind(CodeParameterKind.PathParameters)) is CodeParameter pathParametersParam && - // parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty) - // { - // conventions.AddParametersAssignment(writer, - // pathParametersParam.Type.AllTypes.OfType().First(), - // pathParametersParam.Name.ToFirstCharacterLowerCase(), - // $"this.{pathParametersProperty.Name.ToFirstCharacterLowerCase()}", - // currentMethod.Parameters - // .Where(static x => x.IsOfKind(CodeParameterKind.Path)) - // .Select(x => (x.Type, string.IsNullOrEmpty(x.SerializationName) ? x.Name : x.SerializationName, x.Name.ToFirstCharacterLowerCase())) - // .ToArray()); - // } - // } - + private static string GetDefaultValueLiteralForProperty(CodeProperty codeProperty) + { + if (string.IsNullOrEmpty(codeProperty.DefaultValue)) return string.Empty; + if (codeProperty.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum enumDefinition) + return $"{enumDefinition.Name.ToFirstCharacterUpperCase()}.{codeProperty.DefaultValue.Trim('"').CleanupSymbolName().ToFirstCharacterUpperCase()}"; + return codeProperty.DefaultValue; + } private static void WriteDefensiveStatements(CodeMethod codeElement, LanguageWriter writer) { if (codeElement.IsOfKind(CodeMethodKind.Setter)) return; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index 34bf4691c9..cb3b6ad8a8 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -953,4 +953,34 @@ public void WritesProtectedMethod() Assert.DoesNotContain("export ", result); AssertExtensions.CurlyBracesAreClosed(result, 1); } + [Fact] + public async Task WritesConstructorWithEnumValue() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsAsync = false; + TestHelper.AddSerializationPropertiesToModelClass(parentClass); + method.Kind = CodeMethodKind.Serializer; + var defaultValue = "1024x1024"; + var propName = "size"; + var codeEnum = root.AddEnum(new CodeEnum + { + Name = "pictureSize" + }).First(); + parentClass.AddProperty(new CodeProperty + { + Name = propName, + DefaultValue = defaultValue, + Kind = CodePropertyKind.Custom, + Type = new CodeType { TypeDefinition = codeEnum } + }); + method.IsStatic = true; + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var serializeFunction = root.FindChildByName($"Serialize{parentClass.Name.ToFirstCharacterUpperCase()}"); + writer.Write(serializeFunction); + var result = tw.ToString(); + Assert.Contains($" ?? {codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up + } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index e9e141bf1a..8d93c82bcf 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; using Kiota.Builder.Writers; using Kiota.Builder.Writers.TypeScript; @@ -136,27 +135,6 @@ public void WritesWithUrl() Assert.Throws(() => writer.Write(method)); } [Fact] - public void WritesConstructorWithEnumValue() - {//TODO move to functions - method.Kind = CodeMethodKind.Constructor; - var defaultValue = "1024x1024"; - var propName = "size"; - var codeEnum = new CodeEnum - { - Name = "pictureSize" - }; - parentClass.AddProperty(new CodeProperty - { - Name = propName, - DefaultValue = defaultValue, - Kind = CodePropertyKind.Custom, - Type = new CodeType { TypeDefinition = codeEnum } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"this.{propName.ToFirstCharacterLowerCase()} = {codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up - } - [Fact] public void WritesNameMapperMethod() { method.Kind = CodeMethodKind.QueryParametersMapper; From ccc443a1a7f8632db00c5329a9dfd64f17f0d2ac Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 19 Dec 2023 14:16:45 -0500 Subject: [PATCH 22/40] - fixe3s regression Signed-off-by: Vincent Biret --- tests/Kiota.Builder.Tests/TestHelper.cs | 6 ++++-- .../Writers/TypeScript/CodeFileWriterTests.cs | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Kiota.Builder.Tests/TestHelper.cs b/tests/Kiota.Builder.Tests/TestHelper.cs index e5bdf119e0..bd9725336f 100644 --- a/tests/Kiota.Builder.Tests/TestHelper.cs +++ b/tests/Kiota.Builder.Tests/TestHelper.cs @@ -32,14 +32,16 @@ public static CodeClass CreateModelClass(CodeNamespace codeSpace, string classNa { Name = "DeserializerMethod", ReturnType = new CodeType { }, - Kind = CodeMethodKind.Deserializer + Kind = CodeMethodKind.Deserializer, + IsAsync = false, }; var serializer = new CodeMethod { Name = "SerializerMethod", ReturnType = new CodeType { }, - Kind = CodeMethodKind.Serializer + Kind = CodeMethodKind.Serializer, + IsAsync = false, }; testClass.AddMethod(deserializer); testClass.AddMethod(serializer); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs index a97c130ee2..8d9b84e5b3 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; -using Kiota.Builder.Extensions; using Kiota.Builder.OrderComparers; using Kiota.Builder.Refiners; using Kiota.Builder.Writers; From 3702e171b5912ae85e4d1b8747337b86f5be5d72 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 10:12:23 -0500 Subject: [PATCH 23/40] - fixes factory unit tests Signed-off-by: Vincent Biret --- .../TypeScript/CodeFunctionWriterTests.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index cb3b6ad8a8..ab2e4e9d58 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -103,8 +103,13 @@ public async Task WritesModelFactoryBody() }, Optional = false, }); - var factoryFunction = root.AddFunction(new CodeFunction(factoryMethod)).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var modelInterface = root.FindChildByName("childModel"); + Assert.NotNull(modelInterface); + var parentNS = modelInterface.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + var factoryFunction = parentNS.FindChildByName("createParentModelFromDiscriminatorValue", false); + parentNS.TryAddCodeFile("foo", factoryFunction); writer.Write(factoryFunction); var result = tw.ToString(); Assert.Contains("const mappingValueNode = parseNode.getChildNode(\"@odata.type\")", result); @@ -120,7 +125,6 @@ public async Task WritesModelFactoryBody() [Fact] public async Task DoesntWriteFactorySwitchOnMissingParameter() { - var parentModel = TestHelper.CreateModelClass(root, "parentModel"); var childModel = TestHelper.CreateModelClass(root, "childModel"); childModel.StartBlock.Inherits = new CodeType @@ -128,7 +132,7 @@ public async Task DoesntWriteFactorySwitchOnMissingParameter() Name = "parentModel", TypeDefinition = parentModel, }; - var factoryMethod = parentModel.AddMethod(new CodeMethod + parentModel.AddMethod(new CodeMethod { Name = "factory", Kind = CodeMethodKind.Factory, @@ -138,16 +142,20 @@ public async Task DoesntWriteFactorySwitchOnMissingParameter() TypeDefinition = parentModel, }, IsStatic = true, - }).First(); + }); parentModel.DiscriminatorInformation.AddDiscriminatorMapping("ns.childmodel", new CodeType { Name = "childModel", TypeDefinition = childModel, }); parentModel.DiscriminatorInformation.DiscriminatorPropertyName = "@odata.type"; - var factoryFunction = root.AddFunction(new CodeFunction(factoryMethod)).First(); - await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var modelInterface = root.FindChildByName("childModel"); + Assert.NotNull(modelInterface); + var parentNS = modelInterface.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + var factoryFunction = parentNS.FindChildByName("createParentModelFromDiscriminatorValue", false); + parentNS.TryAddCodeFile("foo", factoryFunction); writer.Write(factoryFunction); var result = tw.ToString(); Assert.DoesNotContain("const mappingValueNode = parseNode.getChildNode(\"@odata.type\")", result); @@ -202,8 +210,13 @@ public async Task DoesntWriteFactorySwitchOnEmptyPropertyName() }, Optional = false, }); - var factoryFunction = root.AddFunction(new CodeFunction(factoryMethod)).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var modelInterface = root.FindChildByName("childModel"); + Assert.NotNull(modelInterface); + var parentNS = modelInterface.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + var factoryFunction = parentNS.FindChildByName("createParentModelFromDiscriminatorValue", false); + parentNS.TryAddCodeFile("foo", factoryFunction); writer.Write(factoryFunction); var result = tw.ToString(); Assert.DoesNotContain("const mappingValueNode = parseNode.getChildNode(\"@odata.type\")", result); @@ -247,8 +260,13 @@ public async Task DoesntWriteFactorySwitchOnEmptyMappings() }, Optional = false, }); - var factoryFunction = root.AddFunction(new CodeFunction(factoryMethod)).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var modelInterface = root.FindChildByName("parentModel"); + Assert.NotNull(modelInterface); + var parentNS = modelInterface.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + var factoryFunction = parentNS.FindChildByName("createParentModelFromDiscriminatorValue", false); + parentNS.TryAddCodeFile("foo", factoryFunction); writer.Write(factoryFunction); var result = tw.ToString(); Assert.DoesNotContain("const mappingValueNode = parseNode.getChildNode(\"@odata.type\")", result); From b3c9d217a9c8680a2d98c778fba5eb7a3785ab7b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 10:27:41 -0500 Subject: [PATCH 24/40] - fixes serialization methods unit tests Signed-off-by: Vincent Biret --- .../TypeScript/CodeFunctionWriterTests.cs | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index ab2e4e9d58..b17c378fb5 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -288,6 +288,10 @@ public async Task WritesInheritedDeSerializerBody() 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($"...deserializeInto{inheritedClass.Name.ToFirstCharacterUpperCase()}", result); @@ -300,6 +304,10 @@ public async Task WritesDeSerializerBody() TestHelper.AddSerializationPropertiesToModelClass(parentClass); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); var deserializerFunction = root.FindChildByName($"deserializeInto{parentClass.Name.ToFirstCharacterUpperCase()}"); + Assert.NotNull(deserializerFunction); + var parentNS = deserializerFunction.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", deserializerFunction); writer.Write(deserializerFunction); var result = tw.ToString(); Assert.Contains("getStringValue", result); @@ -309,6 +317,33 @@ public async Task WritesDeSerializerBody() Assert.Contains("definedInParent", result, StringComparison.OrdinalIgnoreCase); } [Fact] + public async Task WritesDeSerializerBodyWithDefaultValue() + { + var parentClass = TestHelper.CreateModelClass(root, "parentClass"); + TestHelper.AddSerializationPropertiesToModelClass(parentClass); + var defaultValue = "\"Test Value\""; + var propName = "propWithDefaultValue"; + parentClass.AddProperty(new CodeProperty + { + Name = propName, + DefaultValue = defaultValue, + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + }, + }); + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var deserializerFunction = root.FindChildByName($"deserializeInto{parentClass.Name.ToFirstCharacterUpperCase()}"); + Assert.NotNull(deserializerFunction); + var parentNS = deserializerFunction.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", deserializerFunction); + writer.Write(deserializerFunction); + var result = tw.ToString(); + Assert.Contains("?? \"Test Value\"", result); + } + [Fact] public async Task WritesInheritedSerializerBody() { var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; @@ -333,6 +368,10 @@ public async Task WritesSerializerBody() 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(); Assert.Contains("writeStringValue", result); @@ -344,6 +383,38 @@ public async Task WritesSerializerBody() Assert.Contains("definedInParent", result, StringComparison.OrdinalIgnoreCase); } + [Fact] + public async Task WritesSerializerBodyWithDefault() + { + var generationConfiguration = new GenerationConfiguration { Language = GenerationLanguage.TypeScript }; + var parentClass = TestHelper.CreateModelClassInModelsNamespace(generationConfiguration, root, "parentClass"); + var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); + method.Kind = CodeMethodKind.Serializer; + method.IsAsync = false; + var defaultValue = "\"Test Value\""; + var propName = "propWithDefaultValue"; + parentClass.AddProperty(new CodeProperty + { + Name = propName, + DefaultValue = defaultValue, + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = "string", + }, + }); + 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(); + Assert.Contains("?? \"Test Value\"", result); + } + [Fact] public async Task DoesntWriteReadOnlyPropertiesInSerializerBody() { @@ -368,6 +439,10 @@ public async Task DoesntWriteReadOnlyPropertiesInSerializerBody() }); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); var serializeFunction = root.FindChildByName("SerializeTestModel"); + Assert.NotNull(serializeFunction); + var parentNS = serializeFunction.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", serializeFunction); writer.Write(serializeFunction); var result = tw.ToString(); Assert.DoesNotContain("readOnlyProperty", result); @@ -385,7 +460,7 @@ public async Task AddsUsingsForErrorTypesForRequestExecutor() var subNS = root.AddNamespace($"{root.Name}.subns"); // otherwise the import gets trimmed var errorClass = TestHelper.CreateModelClass(subNS, "Error4XX"); errorClass.IsErrorDefinition = true; - var factoryMethod = errorClass.AddMethod(new CodeMethod + errorClass.AddMethod(new CodeMethod { Name = "factory", Kind = CodeMethodKind.Factory, @@ -395,7 +470,7 @@ public async Task AddsUsingsForErrorTypesForRequestExecutor() TypeDefinition = errorClass, }, IsStatic = true, - }).First(); + }); var requestExecutor = requestBuilder.AddMethod(new CodeMethod { Name = "get", @@ -457,6 +532,10 @@ public async Task WritesMessageOverrideOnPrimary() await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); var function = root.FindChildByName("deserializeIntoODataError"); + Assert.NotNull(function); + var parentNS = function.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + parentNS.TryAddCodeFile("foo", function); // When writer.Write(function); @@ -557,43 +636,6 @@ public void WritesApiConstructorWithBackingStore() Assert.Contains("enableBackingStore", result); } [Fact] - public void WritesDefaultValuesInFactory() - { - var parentClass = root.AddClass(new CodeClass - { - Name = "ODataError", - Kind = CodeClassKind.Model, - }).First(); - var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); - method.Kind = CodeMethodKind.Constructor; - method.IsAsync = false; - var defaultValue = "someVal"; - var propName = "propWithDefaultValue"; - parentClass.Kind = CodeClassKind.RequestBuilder; - parentClass.AddProperty(new CodeProperty - { - Name = propName, - DefaultValue = defaultValue, - Kind = CodePropertyKind.Custom, - Type = new CodeType - { - Name = "string", - }, - }); - method.AddParameter(new CodeParameter - { - Name = "pathParameters", - Kind = CodeParameterKind.PathParameters, - Type = new CodeType - { - Name = "Map" - } - }); - writer.Write(method); - var result = tw.ToString(); - Assert.Contains($"this.{propName} = {defaultValue}", result); - } - [Fact] public void DoesNotWriteConstructorWithDefaultFromComposedType() { var parentClass = root.AddClass(new CodeClass From a5d6369148886630f9396f5f64d8606be8cdbdeb Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 11:05:35 -0500 Subject: [PATCH 25/40] - fixes constructor unit tests Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeInterface.cs | 2 +- .../TypeScript/CodeFunctionWriterTests.cs | 119 +++++++----------- 2 files changed, 47 insertions(+), 74 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index c55a091cb9..7446587d98 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -53,7 +53,7 @@ CodeMethodKind.IndexerBackwardCompatibility or { usingToCopy.IsErasable = true; } - result.AddUsing(usings); //TODO pass a list of external imports to remove as we create the interface + result.AddUsing(usings); } if (usingsToAdd is { Length: > 0 } usingsToAddList) result.AddUsing(usingsToAddList); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index b17c378fb5..7c8fcc2960 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -549,8 +549,8 @@ public void WritesApiConstructor() { var parentClass = root.AddClass(new CodeClass { - Name = "ODataError", - Kind = CodeClassKind.Model, + Name = "ApiClient", + Kind = CodeClassKind.RequestBuilder, }).First(); var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); method.Kind = CodeMethodKind.ClientConstructor; @@ -566,59 +566,77 @@ public void WritesApiConstructor() IsExternal = true, } }); - var coreProp = parentClass.AddProperty(new CodeProperty + var requestAdapterProp = parentClass.AddProperty(new CodeProperty { - Name = "core", + Name = "requestAdapter", Kind = CodePropertyKind.RequestAdapter, Type = new CodeType { - Name = "HttpCore", + Name = "RequestAdapter", IsExternal = true, } }).First(); method.AddParameter(new CodeParameter { - Name = "core", + Name = "requestAdapter", Kind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, + Type = requestAdapterProp.Type, }); - method.DeserializerModules = new() { "com.microsoft.kiota.serialization.Deserializer" }; - method.SerializerModules = new() { "com.microsoft.kiota.serialization.Serializer" }; - writer.Write(method); + method.DeserializerModules = ["com.microsoft.kiota.serialization.Deserializer"]; + method.SerializerModules = ["com.microsoft.kiota.serialization.Serializer"]; + method.IsStatic = true; + root.RemoveChildElement(parentClass); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function, CodeInterface.FromRequestBuilder(parentClass)); + writer.Write(function); var result = tw.ToString(); - Assert.Contains("constructor", result); Assert.Contains("registerDefaultSerializer", result); Assert.Contains("registerDefaultDeserializer", result); - Assert.Contains($"[\"baseurl\"] = core.baseUrl", result); Assert.Contains($"baseUrl = \"{method.BaseUrl}\"", result); + Assert.Contains($"\"baseurl\": requestAdapter.baseUrl", result); + Assert.Contains($"apiClientProxifier<", result); + Assert.Contains($"pathParameters", result); + Assert.Contains($"UriTemplate", result); } [Fact] public void WritesApiConstructorWithBackingStore() { var parentClass = root.AddClass(new CodeClass { - Name = "ODataError", - Kind = CodeClassKind.Model, + Name = "ApiClient", + Kind = CodeClassKind.RequestBuilder, }).First(); var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); method.Kind = CodeMethodKind.ClientConstructor; - var coreProp = parentClass.AddProperty(new CodeProperty + method.IsAsync = false; + method.BaseUrl = "https://graph.microsoft.com/v1.0"; + parentClass.AddProperty(new CodeProperty { - Name = "core", + Name = "pathParameters", + Kind = CodePropertyKind.PathParameters, + Type = new CodeType + { + Name = "Dictionary", + IsExternal = true, + } + }); + var requestAdapterProp = parentClass.AddProperty(new CodeProperty + { + Name = "requestAdapter", Kind = CodePropertyKind.RequestAdapter, Type = new CodeType { - Name = "HttpCore", + Name = "RequestAdapter", IsExternal = true, } }).First(); method.AddParameter(new CodeParameter { - Name = "core", + Name = "requestAdapter", Kind = CodeParameterKind.RequestAdapter, - Type = coreProp.Type, + Type = requestAdapterProp.Type, }); - var backingStoreParam = new CodeParameter + method.AddParameter(new CodeParameter { Name = "backingStore", Kind = CodeParameterKind.BackingStore, @@ -627,61 +645,16 @@ public void WritesApiConstructorWithBackingStore() Name = "BackingStore", IsExternal = true, } - }; - method.AddParameter(backingStoreParam); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); - tempWriter.SetTextWriter(tw); - tempWriter.Write(method); - var result = tw.ToString(); - Assert.Contains("enableBackingStore", result); - } - [Fact] - public void DoesNotWriteConstructorWithDefaultFromComposedType() - { - var parentClass = root.AddClass(new CodeClass - { - Name = "ODataError", - Kind = CodeClassKind.Model, - }).First(); - var method = TestHelper.CreateMethod(parentClass, MethodName, ReturnTypeName); - method.Kind = CodeMethodKind.Constructor; - var defaultValue = "\"Test Value\""; - var propName = "size"; - var unionTypeWrapper = root.AddClass(new CodeClass - { - Name = "UnionTypeWrapper", - Kind = CodeClassKind.Model, - OriginalComposedType = new CodeUnionType - { - Name = "UnionTypeWrapper", - }, - DiscriminatorInformation = new() - { - DiscriminatorPropertyName = "@odata.type", - }, - }).First(); - parentClass.AddProperty(new CodeProperty - { - Name = propName, - DefaultValue = defaultValue, - Kind = CodePropertyKind.Custom, - Type = new CodeType { TypeDefinition = unionTypeWrapper } }); - var sType = new CodeType - { - Name = "string", - }; - var arrayType = new CodeType - { - Name = "array", - }; - unionTypeWrapper.OriginalComposedType.AddType(sType); - unionTypeWrapper.OriginalComposedType.AddType(arrayType); - - writer.Write(method); + method.DeserializerModules = ["com.microsoft.kiota.serialization.Deserializer"]; + method.SerializerModules = ["com.microsoft.kiota.serialization.Serializer"]; + method.IsStatic = true; + root.RemoveChildElement(parentClass); + var function = new CodeFunction(method); + root.TryAddCodeFile("foo", function, CodeInterface.FromRequestBuilder(parentClass)); + writer.Write(function); var result = tw.ToString(); - Assert.Contains("constructor", result); - Assert.DoesNotContain(defaultValue, result);//ensure the composed type is not referenced + Assert.Contains("enableBackingStore", result); } [Fact] public void WritesDeprecationInformation() From 608741b86e7d6ec45971e1c397a27131bbbb29a4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 12:37:18 -0500 Subject: [PATCH 26/40] - fixes unit tests for navigation code constant Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 4 +- .../TypeScript/CodeConstantWriterTests.cs | 65 ++++++++++++------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index 0acc18bb32..c7321c290d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -56,7 +56,7 @@ public string? UriTemplate ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; if (!(codeClass.Properties.Any(static x => x.Kind is CodePropertyKind.RequestBuilder) || - codeClass.Methods.Any(x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters))) + codeClass.Methods.Any(static x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters))) return default; return new CodeConstant { @@ -69,7 +69,7 @@ public string? UriTemplate { ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; - if (!codeClass.Methods.Any(x => x.Kind is CodeMethodKind.RequestExecutor or CodeMethodKind.RequestGenerator)) + if (!codeClass.Methods.Any(static x => x.Kind is CodeMethodKind.RequestExecutor or CodeMethodKind.RequestGenerator)) return default; return new CodeConstant { diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index 49d29081b6..e15baa8ceb 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -292,6 +292,7 @@ public void WritesIndexer() { AddRequestProperties(); method.Kind = CodeMethodKind.IndexerBackwardCompatibility; + parentClass.Kind = CodeClassKind.RequestBuilder; method.OriginalIndexer = new() { Name = "indx", @@ -310,37 +311,53 @@ public void WritesIndexer() }, } }; - var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); - writer.Write(constant); - var result = tw.ToString(); - Assert.Contains("this.requestAdapter", result); - Assert.Contains("this.pathParameters", result); - Assert.Contains("id", result); - Assert.Contains("return new", result); - } - [Fact] - public void WritesPathParameterRequestBuilder() - { - AddRequestProperties(); - method.Kind = CodeMethodKind.RequestBuilderWithParameters; + var parentInterface = new CodeInterface + { + Name = "parentClass", + Kind = CodeInterfaceKind.RequestBuilder + }; method.AddParameter(new CodeParameter { - Name = "pathParam", - Kind = CodeParameterKind.Path, + Name = "id", Type = new CodeType { - Name = "string" - } + Name = "string", + IsNullable = true, + }, + SerializationName = "foo-id", + Kind = CodeParameterKind.Path }); - var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + var parentNS = parentClass.GetImmediateParentOfType(); + Assert.NotNull(parentNS); + var childNS = parentNS.AddNamespace($"{parentNS.Name}.childNS"); + childNS.TryAddCodeFile("foo", + new CodeConstant + { + Name = "SomecustomtypeUriTemplate", + Kind = CodeConstantKind.UriTemplate, + }, + new CodeConstant + { + Name = "SomecustomtypeNavigationMetadata", + Kind = CodeConstantKind.NavigationMetadata, + }, + new CodeConstant + { + Name = "SomecustomtypeRequestsMetadata", + Kind = CodeConstantKind.RequestsMetadata, + }); + parentInterface.AddMethod(method); + var constant = CodeConstant.FromRequestBuilderToNavigationMetadata(parentClass); + Assert.NotNull(constant); + parentNS.TryAddCodeFile("foo", constant, parentInterface); writer.Write(constant); var result = tw.ToString(); - Assert.Contains("this.requestAdapter", result); - Assert.Contains("this.pathParameters", result); - Assert.Contains("pathParam", result); - Assert.Contains("return new", result); + Assert.Contains("export const ParentClassNavigationMetadata: Record, NavigationMetadata> = {", result); + Assert.Contains("methodName", result); + Assert.Contains("uriTemplate: SomecustomtypeUriTemplate", result); + Assert.Contains("requestsMetadata: SomecustomtypeRequestsMetadata", result); + Assert.Contains("navigationMetadata: SomecustomtypeNavigationMetadata", result); + Assert.Contains("pathParametersMappings: [\"foo-id\"]", result); } private void AddRequestProperties() { From 6d4f17c3a1ea0e5baf7034db66cecf02de292d7c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 14:08:05 -0500 Subject: [PATCH 27/40] - fixes request generator and executor constant unit tests in typescript Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeConstantWriter.cs | 19 ++- .../TypeScript/CodeConstantWriterTests.cs | 111 ++++++++++++------ 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 52c0f890e6..515ded934e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -112,9 +112,13 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri if (!isVoid) writer.WriteLine($"responseBodyFactory: {GetTypeFactory(isVoid, isStream, executorMethod, writer)},"); if (!string.IsNullOrEmpty(executorMethod.RequestBodyContentType)) - writer.WriteLine($"requestBodyContentType: \"{executorMethod.RequestBodyContentType}\","); //TODO add support for configurable content type - if (executorMethod.Parameters.FirstOrDefault(static x => x.Kind is CodeParameterKind.RequestBody) is CodeParameter requestBody && GetBodySerializer(requestBody) is string bodySerializer) - writer.WriteLine($"requestBodySerializer: {bodySerializer},"); + writer.WriteLine($"requestBodyContentType: \"{executorMethod.RequestBodyContentType}\","); + if (executorMethod.Parameters.FirstOrDefault(static x => x.Kind is CodeParameterKind.RequestBody) is CodeParameter requestBody) + { + if (GetBodySerializer(requestBody) is string bodySerializer) + writer.WriteLine($"requestBodySerializer: {bodySerializer},"); + writer.WriteLine($"requestInformationContentSetMethod: {GetRequestContentSetterMethodName(requestBody)},"); + } if (codeElement.Parent is CodeFile parentCodeFile && parentCodeFile.FindChildByName(codeElement.Name.Replace("RequestsMetadata", $"{executorMethod.Name.ToFirstCharacterUpperCase()}QueryParametersMapper", StringComparison.Ordinal), false) is CodeConstant mapperConstant) writer.WriteLine($"queryParametersMapper: {mapperConstant.Name.ToFirstCharacterUpperCase()},"); @@ -122,6 +126,15 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri } writer.CloseBlock("};"); } + private string GetRequestContentSetterMethodName(CodeParameter requestBody) + { + if (requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) + return "\"setStreamContent\""; + + if (requestBody.Type is CodeType currentType && (currentType.TypeDefinition is CodeInterface || currentType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) + return "\"setContentFromParsable\""; + return "\"setContentFromScalar\""; + } private string? GetBodySerializer(CodeParameter requestBody) { if (requestBody.Type is CodeType currentType && (currentType.TypeDefinition is CodeInterface || currentType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index e15baa8ceb..b65878ae51 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -140,16 +140,18 @@ public void DoesNotCreateDictionaryOnEmptyErrorMapping() [Fact] public void WritesRequestGeneratorBodyForMultipart() { - method.Kind = CodeMethodKind.RequestGenerator; + parentClass.Kind = CodeClassKind.RequestBuilder; + method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Post; AddRequestProperties(); AddRequestBodyParameters(); method.Parameters.First(static x => x.IsOfKind(CodeParameterKind.RequestBody)).Type = new CodeType { Name = "MultipartBody", IsExternal = true }; var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + root.TryAddCodeFile("foo", constant); writer.Write(constant); var result = tw.ToString(); Assert.Contains("setContentFromParsable", result); + Assert.Contains("serializeMultipartBody", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] @@ -169,49 +171,78 @@ public void WritesRequestExecutorBodyForCollections() [Fact] public void WritesRequestGeneratorBodyForScalar() { - method.Kind = CodeMethodKind.RequestGenerator; + parentClass.Kind = CodeClassKind.RequestBuilder; + method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; AddRequestProperties(); AddRequestBodyParameters(); - method.AcceptedResponseTypes.Add("application/json"); + var generatorMethod = parentClass.AddMethod(new CodeMethod + { + Name = "toGetRequestInformation", + HttpMethod = HttpMethod.Get, + Kind = CodeMethodKind.RequestGenerator, + ReturnType = new CodeType + { + Name = "RequestInformation", + IsExternal = true, + }, + }).First(); + generatorMethod.AcceptedResponseTypes.Add("application/json"); var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + root.TryAddCodeFile("foo", constant); writer.Write(constant); var result = tw.ToString(); - Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters)", result); - Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); - Assert.Contains("requestInfo.configure", result); - Assert.Contains("setContentFromScalar", result); - Assert.Contains("return requestInfo;", result); + Assert.Contains("export const", result); + Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("responseBodyContentType: \"application/json\"", result); + Assert.Contains("requestInformationContentSetMethod: \"setContentFromScalar", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] public void WritesRequestGeneratorBodyForParsable() { - method.Kind = CodeMethodKind.RequestGenerator; + parentClass.Kind = CodeClassKind.RequestBuilder; + method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; AddRequestProperties(); AddRequestBodyParameters(true); - method.AcceptedResponseTypes.Add("application/json"); + var generatorMethod = parentClass.AddMethod(new CodeMethod + { + Name = "toGetRequestInformation", + HttpMethod = HttpMethod.Get, + Kind = CodeMethodKind.RequestGenerator, + ReturnType = new CodeType + { + Name = "RequestInformation", + IsExternal = true, + }, + }).First(); + var bodyParameter = method.Parameters.First(static x => x.Kind is CodeParameterKind.RequestBody); + bodyParameter.Type = new CodeType + { + Name = "SomeComplexTypeForRequestBody", + TypeDefinition = new CodeInterface + { + Name = "SomeComplexTypeForRequestBody", + Kind = CodeInterfaceKind.Model, + }, + }; + generatorMethod.AcceptedResponseTypes.Add("application/json"); var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + root.TryAddCodeFile("foo", constant); writer.Write(constant); var result = tw.ToString(); - Assert.Contains("const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters", result); - Assert.Contains("requestInfo.headers.tryAdd(\"Accept\", \"application/json\")", result); - Assert.Contains("requestInfo.configure", result); - Assert.Contains("setContentFromParsable", result); - Assert.Contains("return requestInfo;", result); + Assert.Contains("export const", result); + Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("responseBodyContentType: \"application/json\"", result); + Assert.Contains("requestInformationContentSetMethod: \"setContentFromParsable", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] public void WritesRequestGeneratorBodyKnownRequestBodyType() { - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(true); - method.Kind = CodeMethodKind.RequestGenerator; + parentClass.Kind = CodeClassKind.RequestBuilder; + method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Post; AddRequestProperties(); AddRequestBodyParameters(false); @@ -222,21 +253,20 @@ public void WritesRequestGeneratorBodyKnownRequestBodyType() }; method.RequestBodyContentType = "application/json"; var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + root.TryAddCodeFile("foo", constant); writer.Write(constant); var result = tw.ToString(); - Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); - Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("export const", result); + Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("requestBodyContentType: \"application/json\"", result); + Assert.Contains("requestInformationContentSetMethod: \"setStreamContent", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] public void WritesRequestGeneratorBodyUnknownRequestBodyType() { - method.Kind = CodeMethodKind.RequestGenerator; - method.HttpMethod = HttpMethod.Get; - AddRequestProperties(); - AddRequestBodyParameters(true); - method.Kind = CodeMethodKind.RequestGenerator; + parentClass.Kind = CodeClassKind.RequestBuilder; + method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Post; AddRequestProperties(); AddRequestBodyParameters(false); @@ -256,17 +286,20 @@ public void WritesRequestGeneratorBodyUnknownRequestBodyType() Kind = CodeParameterKind.RequestBodyContentType, }); var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); - parentClass.GetImmediateParentOfType().AddConstant(constant); + root.TryAddCodeFile("foo", constant); writer.Write(constant); var result = tw.ToString(); + Assert.Contains("export const", result); + Assert.Contains("RequestsMetadata: Record = {", result); + Assert.DoesNotContain("requestBodyContentType: \"application/json\"", result); + Assert.Contains("requestInformationContentSetMethod: \"setStreamContent", result); Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); - Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); - Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] public void WritesRequestExecutorBody() { + parentClass.Kind = CodeClassKind.RequestBuilder; method.Kind = CodeMethodKind.RequestExecutor; method.HttpMethod = HttpMethod.Get; var error4XX = root.AddClass(new CodeClass @@ -285,7 +318,15 @@ public void WritesRequestExecutorBody() method.AddErrorMapping("5XX", new CodeType { Name = "Error5XX", TypeDefinition = error5XX }); method.AddErrorMapping("403", new CodeType { Name = "Error403", TypeDefinition = error403 }); AddRequestBodyParameters(); - Assert.Throws(() => writer.Write(method)); + var constant = CodeConstant.FromRequestBuilderToRequestsMetadata(parentClass); + root.TryAddCodeFile("foo", constant); + writer.Write(constant); + var result = tw.ToString(); + Assert.Contains("errorMappings: {", result); + Assert.Contains("4XX\": createError4XXFromDiscriminatorValue", result); + Assert.Contains("5XX\": createError5XXFromDiscriminatorValue", result); + Assert.Contains("403\": createError403FromDiscriminatorValue", result); + Assert.Contains(" as Record>", result); } [Fact] public void WritesIndexer() From 7ab69039de1884480aab7d09edd2bccfd3a9667f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 20 Dec 2023 14:09:28 -0500 Subject: [PATCH 28/40] - formatting --- .../Refiners/TypeScriptRefiner.cs | 18 ++++++++++++------ .../TypeScript/CodeConstantWriterTests.cs | 3 +-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index ef04c962bb..d38f945626 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -267,26 +267,32 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); } private static readonly CodeUsing[] requestBuilderUsings = [ - new CodeUsing { + new CodeUsing + { Name = "RequestMetadata", IsErasable = true, - Declaration = new CodeType { + Declaration = new CodeType + { Name = AbstractionsPackageName, IsExternal = true, }, }, - new CodeUsing { + new CodeUsing + { Name = "NavigationMetadata", IsErasable = true, - Declaration = new CodeType { + Declaration = new CodeType + { Name = AbstractionsPackageName, IsExternal = true, }, }, - new CodeUsing { + new CodeUsing + { Name = "KeysToExcludeForNavigationMetadata", IsErasable = true, - Declaration = new CodeType { + Declaration = new CodeType + { Name = AbstractionsPackageName, IsExternal = true, }, diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index b65878ae51..d2a429b2eb 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -2,10 +2,9 @@ using System.IO; using System.Linq; using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; using Kiota.Builder.Writers; using Kiota.Builder.Writers.TypeScript; -using Kiota.Builder.Extensions; - using Xunit; namespace Kiota.Builder.Tests.Writers.TypeScript; From c3b782d2ccd8cab946a2dfd25029887e59a6e4eb Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jan 2024 16:23:56 -0500 Subject: [PATCH 29/40] Apply suggestions from code review Co-authored-by: Eastman --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 1 + src/Kiota.Builder/CodeDOM/CodeInterface.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index c7321c290d..df2f8ab287 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -49,6 +49,7 @@ public string? UriTemplate Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}UriTemplate", Kind = CodeConstantKind.UriTemplate, UriTemplate = urlTemplateProperty.DefaultValue, + OriginalCodeElement = codeClass }; } public static CodeConstant? FromRequestBuilderToNavigationMetadata(CodeClass codeClass) diff --git a/src/Kiota.Builder/CodeDOM/CodeInterface.cs b/src/Kiota.Builder/CodeDOM/CodeInterface.cs index 7446587d98..08ce97203a 100644 --- a/src/Kiota.Builder/CodeDOM/CodeInterface.cs +++ b/src/Kiota.Builder/CodeDOM/CodeInterface.cs @@ -49,7 +49,7 @@ CodeMethodKind.IndexerBackwardCompatibility or if (codeClass.Usings.ToArray() is { Length: > 0 } usings) { - foreach (var usingToCopy in usings.Where(static x => x.Declaration is not null && x.Declaration.TypeDefinition is CodeInterface or CodeClass { Kind: CodeClassKind.RequestBuilder })) + foreach (var usingToCopy in usings.Where(static x => x.Declaration?.TypeDefinition is CodeInterface or CodeClass { Kind: CodeClassKind.RequestBuilder })) { usingToCopy.IsErasable = true; } From e572329e1a14b22145935cf22a8ff4433db2b72d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 10 Jan 2024 15:06:31 -0500 Subject: [PATCH 30/40] - code linting Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index 9da2ec86ee..8286e3cf67 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -200,7 +200,7 @@ public void RemoveUsings(params CodeUsing[] codeUsings) } public void RemoveUsingsByDeclarationName(params string[] names) { - if (names == null || names.Any(x => string.IsNullOrEmpty(x))) + if (names == null || names.Any(string.IsNullOrEmpty)) throw new ArgumentNullException(nameof(names)); var namesAsHashSet = names.ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var usingToRemove in usings.Keys.Where(x => !string.IsNullOrEmpty(x.Declaration?.Name) && namesAsHashSet.Contains(x.Declaration!.Name))) From 65af09cd1e04044204e43a2939a192b0fef55ce2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 10 Jan 2024 15:06:57 -0500 Subject: [PATCH 31/40] - adds the ability for constants to have usings and documentation Signed-off-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index df2f8ab287..0747730059 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -4,8 +4,10 @@ namespace Kiota.Builder.CodeDOM; -public class CodeConstant : CodeTerminalWithKind +public class CodeConstant : CodeTerminalWithKind, IDocumentedElement { + public BlockDeclaration StartBlock { get; set; } = new(); + public void AddUsing(params CodeUsing[] codeUsings) => StartBlock.AddUsings(codeUsings); public CodeElement? OriginalCodeElement { get; @@ -16,6 +18,11 @@ public string? UriTemplate { get; init; } + /// + public CodeDocumentation Documentation + { + get; set; + } = new(); #pragma warning restore CA1056 // URI-like properties should not be strings public static CodeConstant? FromQueryParametersMapping(CodeInterface source) { @@ -52,32 +59,38 @@ public string? UriTemplate OriginalCodeElement = codeClass }; } - public static CodeConstant? FromRequestBuilderToNavigationMetadata(CodeClass codeClass) + public static CodeConstant? FromRequestBuilderToNavigationMetadata(CodeClass codeClass, CodeUsing[]? usingsToAdd = default) { ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; if (!(codeClass.Properties.Any(static x => x.Kind is CodePropertyKind.RequestBuilder) || codeClass.Methods.Any(static x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters))) return default; - return new CodeConstant + var result = new CodeConstant { Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}NavigationMetadata", Kind = CodeConstantKind.NavigationMetadata, OriginalCodeElement = codeClass, }; + if (usingsToAdd is { Length: > 0 } usingsToAddList) + result.AddUsing(usingsToAddList); + return result; } - public static CodeConstant? FromRequestBuilderToRequestsMetadata(CodeClass codeClass) + public static CodeConstant? FromRequestBuilderToRequestsMetadata(CodeClass codeClass, CodeUsing[]? usingsToAdd = default) { ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; if (!codeClass.Methods.Any(static x => x.Kind is CodeMethodKind.RequestExecutor or CodeMethodKind.RequestGenerator)) return default; - return new CodeConstant + var result = new CodeConstant { Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}RequestsMetadata", Kind = CodeConstantKind.RequestsMetadata, OriginalCodeElement = codeClass, }; + if (usingsToAdd is { Length: > 0 } usingsToAddList) + result.AddUsing(usingsToAddList); + return result; } } public enum CodeConstantKind From 591dfdcbb81f44e40bd69d1a34cbe7ed34ae5435 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 10 Jan 2024 15:16:09 -0500 Subject: [PATCH 32/40] - avoids projecting unnecessary usings for navigation and request metadata Signed-off-by: Vincent Biret --- .../Refiners/TypeScriptRefiner.cs | 28 +++++++++---------- .../TypeScript/CodeFileDeclarationWriter.cs | 1 + 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index d38f945626..07a96f1aa3 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -111,11 +111,8 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance } ); AddSerializationModulesImport(generatedCode, - [$"{AbstractionsPackageName}.registerDefaultSerializer", - $"{AbstractionsPackageName}.enableBackingStoreForSerializationWriterFactory", - $"{AbstractionsPackageName}.SerializationWriterFactoryRegistry"], - [$"{AbstractionsPackageName}.registerDefaultDeserializer", - $"{AbstractionsPackageName}.ParseNodeFactoryRegistry"]); + [$"{AbstractionsPackageName}.registerDefaultSerializer"], + [$"{AbstractionsPackageName}.registerDefaultDeserializer"]); cancellationToken.ThrowIfCancellationRequested(); AddDiscriminatorMappingsUsingsToParentClasses( generatedCode, @@ -266,10 +263,10 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && return null; return codeNamespace.TryAddCodeFile(codeInterface.Name, [codeInterface, .. functions]); } - private static readonly CodeUsing[] requestBuilderUsings = [ + private static readonly CodeUsing[] navigationMetadataUsings = [ new CodeUsing { - Name = "RequestMetadata", + Name = "NavigationMetadata", IsErasable = true, Declaration = new CodeType { @@ -279,29 +276,31 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && }, new CodeUsing { - Name = "NavigationMetadata", + Name = "KeysToExcludeForNavigationMetadata", IsErasable = true, Declaration = new CodeType { Name = AbstractionsPackageName, IsExternal = true, }, - }, + }]; + private static readonly CodeUsing[] requestMetadataUsings = [ new CodeUsing { - Name = "KeysToExcludeForNavigationMetadata", + Name = "RequestMetadata", IsErasable = true, Declaration = new CodeType { Name = AbstractionsPackageName, IsExternal = true, }, - }]; + }, + ]; private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass codeClass, CodeNamespace codeNamespace) { - if (CodeConstant.FromRequestBuilderToRequestsMetadata(codeClass) is CodeConstant requestsMetadataConstant) + if (CodeConstant.FromRequestBuilderToRequestsMetadata(codeClass, requestMetadataUsings) is CodeConstant requestsMetadataConstant) codeNamespace.AddConstant(requestsMetadataConstant); - if (CodeConstant.FromRequestBuilderToNavigationMetadata(codeClass) is CodeConstant navigationConstant) + if (CodeConstant.FromRequestBuilderToNavigationMetadata(codeClass, navigationMetadataUsings) is CodeConstant navigationConstant) codeNamespace.AddConstant(navigationConstant); if (CodeConstant.FromRequestBuilderClassToUriTemplate(codeClass) is CodeConstant uriTemplateConstant) codeNamespace.AddConstant(uriTemplateConstant); @@ -320,7 +319,7 @@ private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass cod }, }); } - var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass, requestBuilderUsings); + var interfaceDeclaration = CodeInterface.FromRequestBuilder(codeClass); codeNamespace.RemoveChildElement(codeClass); codeNamespace.AddInterface(interfaceDeclaration); return interfaceDeclaration; @@ -397,6 +396,7 @@ private static IEnumerable GetUsingsFromCodeElement(CodeElement codeE CodeInterface ci => ci.Usings, CodeEnum ce => ce.Usings, CodeClass cc => cc.Usings, + CodeConstant codeConstant => codeConstant.StartBlock.Usings, _ => Enumerable.Empty() }; } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index d3069e3828..cf712fc289 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -26,6 +26,7 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW CodeFunction f => f.StartBlock.Usings, CodeInterface ci => ci.Usings, CodeClass cc => cc.Usings, + CodeConstant c => c.StartBlock.Usings, _ => Enumerable.Empty() }; } From 7450cae8d4e48dd20910393cbbdd1029b0bd8e17 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 10 Jan 2024 15:22:33 -0500 Subject: [PATCH 33/40] - adds missing documentation for code constants --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 10 ++++++++-- .../Writers/TypeScript/CodeConstantWriter.cs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index 0747730059..aadc5f663f 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -29,12 +29,14 @@ public CodeDocumentation Documentation ArgumentNullException.ThrowIfNull(source); if (source.Kind is not CodeInterfaceKind.QueryParameters) throw new InvalidOperationException("Cannot create a query parameters constant from a non query parameters interface"); if (!source.Properties.Any(static x => !string.IsNullOrEmpty(x.SerializationName))) return default; - return new CodeConstant + var result = new CodeConstant { Name = $"{source.Name.ToFirstCharacterLowerCase()}Mapper", Kind = CodeConstantKind.QueryParametersMapper, OriginalCodeElement = source, }; + result.Documentation.Description = "Mapper for query parameters from symbol name to serialization name represented as a constant."; + return result; } public static CodeConstant? FromCodeEnum(CodeEnum source) { @@ -51,13 +53,15 @@ public CodeDocumentation Documentation ArgumentNullException.ThrowIfNull(codeClass); if (codeClass.Kind != CodeClassKind.RequestBuilder) return default; if (codeClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty) throw new InvalidOperationException($"Couldn't find the url template property for class {codeClass.Name}"); - return new CodeConstant + var result = new CodeConstant { Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}UriTemplate", Kind = CodeConstantKind.UriTemplate, UriTemplate = urlTemplateProperty.DefaultValue, OriginalCodeElement = codeClass }; + result.Documentation.Description = "Uri template for the request builder."; + return result; } public static CodeConstant? FromRequestBuilderToNavigationMetadata(CodeClass codeClass, CodeUsing[]? usingsToAdd = default) { @@ -72,6 +76,7 @@ public CodeDocumentation Documentation Kind = CodeConstantKind.NavigationMetadata, OriginalCodeElement = codeClass, }; + result.Documentation.Description = "Metadata for all the navigation properties in the request builder."; if (usingsToAdd is { Length: > 0 } usingsToAddList) result.AddUsing(usingsToAddList); return result; @@ -88,6 +93,7 @@ public CodeDocumentation Documentation Kind = CodeConstantKind.RequestsMetadata, OriginalCodeElement = codeClass, }; + result.Documentation.Description = "Metadata for all the requests in the request builder."; if (usingsToAdd is { Length: > 0 } usingsToAddList) result.AddUsing(usingsToAddList); return result; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 515ded934e..c462dbb9e4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -11,6 +11,7 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); + conventions.WriteLongDescription(codeElement, writer); switch (codeElement.Kind) { case CodeConstantKind.QueryParametersMapper: From 750299540e263a77b4d9dfd29a1feb3d82dc8098 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 11 Jan 2024 10:28:43 -0500 Subject: [PATCH 34/40] - removes unecessary import of serialization methods in typescript --- .../Refiners/TypeScriptRefiner.cs | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 07a96f1aa3..7fd5414f9c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -504,7 +504,7 @@ private static bool IsMultipartBody(CodeParameter p) => private static bool HasMultipartBody(CodeMethod m) => m.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator) && m.Parameters.Any(IsMultipartBody); - // for Kiota abstraction library if the code is not required for runtime purposes e.g. interfaces then the IsErassable flag is set to true + // for Kiota abstraction library if the code is not required for runtime purposes e.g. interfaces then the IsErasable flag is set to true private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = { new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor, AbstractionsPackageName, true, "RequestAdapter"), @@ -853,9 +853,7 @@ private static (CodeFunction, CodeFunction) GetSerializationFunctionsForNamespac } private static void AddSerializationUsingToRequestBuilder(CodeClass modelClass, CodeClass targetClass) { - var serializationFunctions = GetSerializationFunctionsForNamespace(modelClass); - var serializer = serializationFunctions.Item1; - var deserializer = serializationFunctions.Item2; + var (serializer, _) = GetSerializationFunctionsForNamespace(modelClass); if (serializer.Parent is not null) { targetClass.AddUsing(new CodeUsing @@ -868,19 +866,6 @@ private static void AddSerializationUsingToRequestBuilder(CodeClass modelClass, } }); } - - if (deserializer.Parent is not null) - { - targetClass.AddUsing(new CodeUsing - { - Name = deserializer.Parent.Name, - Declaration = new CodeType - { - Name = deserializer.Name, - TypeDefinition = deserializer - } - }); - } } private static void ProcessModelsAssociatedWithMethods(CodeMethod codeMethod, CodeClass requestBuilderClass, Func interfaceNamingCallback) @@ -924,20 +909,6 @@ returnType.TypeDefinition is CodeClass returnClass && } } } - if (codeMethod.ErrorMappings.Any()) - { - ProcessModelClassAssociatedWithErrorMappings(codeMethod); - } - } - private static void ProcessModelClassAssociatedWithErrorMappings(CodeMethod codeMethod) - { - foreach (var errorMapping in codeMethod.ErrorMappings) - { - if (errorMapping.Value is CodeType codeType && codeType.TypeDefinition is CodeClass errorMappingClass && codeMethod.Parent is CodeClass parentClass) - { - AddSerializationUsingToRequestBuilder(errorMappingClass, parentClass); - } - } } private static void ProcessModelClassAssociatedWithRequestGenerator(CodeMethod codeMethod, CodeClass requestBodyClass) From 04a0915f206b33887d9b9c9c8eb7b0f1e1b46063 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 11 Jan 2024 13:32:30 -0500 Subject: [PATCH 35/40] - code-linting: add constants to avoid multiple definitions --- src/Kiota.Builder/CodeDOM/CodeConstant.cs | 9 ++++++--- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 6 +++--- .../Writers/TypeScript/CodeConstantWriter.cs | 10 +++++----- .../Writers/TypeScript/CodeFunctionWriter.cs | 6 +++--- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeConstant.cs b/src/Kiota.Builder/CodeDOM/CodeConstant.cs index aadc5f663f..0d6ff8ed76 100644 --- a/src/Kiota.Builder/CodeDOM/CodeConstant.cs +++ b/src/Kiota.Builder/CodeDOM/CodeConstant.cs @@ -48,6 +48,9 @@ public CodeDocumentation Documentation OriginalCodeElement = source, }; } + internal const string UriTemplateSuffix = "UriTemplate"; + internal const string RequestsMetadataSuffix = "RequestsMetadata"; + internal const string NavigationMetadataSuffix = "NavigationMetadata"; public static CodeConstant? FromRequestBuilderClassToUriTemplate(CodeClass codeClass) { ArgumentNullException.ThrowIfNull(codeClass); @@ -55,7 +58,7 @@ public CodeDocumentation Documentation if (codeClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty) throw new InvalidOperationException($"Couldn't find the url template property for class {codeClass.Name}"); var result = new CodeConstant { - Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}UriTemplate", + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}{UriTemplateSuffix}", Kind = CodeConstantKind.UriTemplate, UriTemplate = urlTemplateProperty.DefaultValue, OriginalCodeElement = codeClass @@ -72,7 +75,7 @@ public CodeDocumentation Documentation return default; var result = new CodeConstant { - Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}NavigationMetadata", + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}{NavigationMetadataSuffix}", Kind = CodeConstantKind.NavigationMetadata, OriginalCodeElement = codeClass, }; @@ -89,7 +92,7 @@ public CodeDocumentation Documentation return default; var result = new CodeConstant { - Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}RequestsMetadata", + Name = $"{codeClass.Name.ToFirstCharacterLowerCase()}{RequestsMetadataSuffix}", Kind = CodeConstantKind.RequestsMetadata, OriginalCodeElement = codeClass, }; diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 7fd5414f9c..1bfaf13b77 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -363,9 +363,9 @@ private static void GenerateRequestBuilderCodeFile(CodeInterface codeInterface, .OfType() .ToArray(); - var navigationConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}NavigationMetadata", false); - var requestsMetadataConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}RequestsMetadata", false); - var uriTemplateConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}UriTemplate", false); + var navigationConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}{CodeConstant.NavigationMetadataSuffix}", false); + var requestsMetadataConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}{CodeConstant.RequestsMetadataSuffix}", false); + var uriTemplateConstant = codeNamespace.FindChildByName($"{codeInterface.Name.ToFirstCharacterLowerCase()}{CodeConstant.UriTemplateSuffix}", false); var proxyConstants = new[] { navigationConstant, requestsMetadataConstant, uriTemplateConstant } .OfType() diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index c462dbb9e4..e50ed0834a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -37,7 +37,7 @@ public override void WriteCodeElement(CodeConstant codeElement, LanguageWriter w private void WriteNavigationMetadataConstant(CodeConstant codeElement, LanguageWriter writer) { if (codeElement.OriginalCodeElement is not CodeClass codeClass) throw new InvalidOperationException("Original CodeElement cannot be null"); - if (codeElement.Parent is not CodeFile parentCodeFile || parentCodeFile.FindChildByName(codeElement.Name.Replace("NavigationMetadata", string.Empty, StringComparison.Ordinal), false) is not CodeInterface currentInterface) + if (codeElement.Parent is not CodeFile parentCodeFile || parentCodeFile.FindChildByName(codeElement.Name.Replace(CodeConstant.NavigationMetadataSuffix, string.Empty, StringComparison.Ordinal), false) is not CodeInterface currentInterface) throw new InvalidOperationException("Couldn't find the associated interface for the navigation metadata constant"); var navigationMethods = codeClass.Methods .Where(static x => x.Kind is CodeMethodKind.IndexerBackwardCompatibility or CodeMethodKind.RequestBuilderWithParameters) @@ -71,11 +71,11 @@ private void WriteNavigationMetadataConstant(CodeConstant codeElement, LanguageW private static void WriteNavigationMetadataEntry(CodeNamespace parentNamespace, LanguageWriter writer, string requestBuilderName, string[]? pathParameters = null) { - if (parentNamespace.FindChildByName($"{requestBuilderName}UriTemplate", 3) is CodeConstant uriTemplateConstant && uriTemplateConstant.Kind is CodeConstantKind.UriTemplate) + if (parentNamespace.FindChildByName($"{requestBuilderName}{CodeConstant.UriTemplateSuffix}", 3) is CodeConstant uriTemplateConstant && uriTemplateConstant.Kind is CodeConstantKind.UriTemplate) writer.WriteLine($"uriTemplate: {uriTemplateConstant.Name.ToFirstCharacterUpperCase()},"); - if (parentNamespace.FindChildByName($"{requestBuilderName}RequestsMetadata", 3) is CodeConstant requestsMetadataConstant && requestsMetadataConstant.Kind is CodeConstantKind.RequestsMetadata) + if (parentNamespace.FindChildByName($"{requestBuilderName}{CodeConstant.RequestsMetadataSuffix}", 3) is CodeConstant requestsMetadataConstant && requestsMetadataConstant.Kind is CodeConstantKind.RequestsMetadata) writer.WriteLine($"requestsMetadata: {requestsMetadataConstant.Name.ToFirstCharacterUpperCase()},"); - if (parentNamespace.FindChildByName($"{requestBuilderName}NavigationMetadata", 3) is CodeConstant navigationMetadataConstant && navigationMetadataConstant.Kind is CodeConstantKind.NavigationMetadata) + if (parentNamespace.FindChildByName($"{requestBuilderName}{CodeConstant.NavigationMetadataSuffix}", 3) is CodeConstant navigationMetadataConstant && navigationMetadataConstant.Kind is CodeConstantKind.NavigationMetadata) writer.WriteLine($"navigationMetadata: {navigationMetadataConstant.Name.ToFirstCharacterUpperCase()},"); if (pathParameters is { Length: > 0 }) writer.WriteLine($"pathParametersMappings: [{string.Join(", ", pathParameters)}],"); @@ -121,7 +121,7 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri writer.WriteLine($"requestInformationContentSetMethod: {GetRequestContentSetterMethodName(requestBody)},"); } if (codeElement.Parent is CodeFile parentCodeFile && - parentCodeFile.FindChildByName(codeElement.Name.Replace("RequestsMetadata", $"{executorMethod.Name.ToFirstCharacterUpperCase()}QueryParametersMapper", StringComparison.Ordinal), false) is CodeConstant mapperConstant) + parentCodeFile.FindChildByName(codeElement.Name.Replace(CodeConstant.RequestsMetadataSuffix, $"{executorMethod.Name.ToFirstCharacterUpperCase()}QueryParametersMapper", StringComparison.Ordinal), false) is CodeConstant mapperConstant) writer.WriteLine($"queryParametersMapper: {mapperConstant.Name.ToFirstCharacterUpperCase()},"); writer.CloseBlock("},"); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 0f93b86e8b..aa8947f083 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -69,9 +69,9 @@ private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod meth writer.WriteLine($"{requestAdapterArgumentName}.enableBackingStore({backingStoreParameterName.ToFirstCharacterLowerCase()});"); if (parentFile.Interfaces.FirstOrDefault(static x => x.Kind is CodeInterfaceKind.RequestBuilder) is CodeInterface codeInterface) { - var uriTemplateConstantName = $"{codeInterface.Name.ToFirstCharacterUpperCase()}UriTemplate"; - var navigationMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}NavigationMetadata", false) is { } navConstant ? navConstant.Name.ToFirstCharacterUpperCase() : "undefined"; - var requestsMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}RequestsMetadata", false) is { } reqConstant ? reqConstant.Name.ToFirstCharacterUpperCase() : "undefined"; + var uriTemplateConstantName = $"{codeInterface.Name.ToFirstCharacterUpperCase()}{CodeConstant.UriTemplateSuffix}"; + var navigationMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}{CodeConstant.NavigationMetadataSuffix}", false) is { } navConstant ? navConstant.Name.ToFirstCharacterUpperCase() : "undefined"; + var requestsMetadataConstantName = parentFile.FindChildByName($"{codeInterface.Name.ToFirstCharacterUpperCase()}{CodeConstant.RequestsMetadataSuffix}", false) is { } reqConstant ? reqConstant.Name.ToFirstCharacterUpperCase() : "undefined"; writer.WriteLine($"return apiClientProxifier<{codeInterface.Name.ToFirstCharacterUpperCase()}>({requestAdapterArgumentName}, pathParameters, {uriTemplateConstantName}, {navigationMetadataConstantName}, {requestsMetadataConstantName});"); } } From 3426ad7c435a57b0481bf390248ba4825218920e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 12 Jan 2024 13:24:04 -0500 Subject: [PATCH 36/40] - removes strings for typescript operation names - removes strings for typescript navigation metadata Signed-off-by: Vincent Biret --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeConstantWriter.cs | 8 ++++---- .../Writers/TypeScript/CodeConstantWriterTests.cs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 1bfaf13b77..a6da73b8a8 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -287,7 +287,7 @@ codeFunction.OriginalLocalMethod.Kind is CodeMethodKind.Factory && private static readonly CodeUsing[] requestMetadataUsings = [ new CodeUsing { - Name = "RequestMetadata", + Name = "RequestsMetadata", IsErasable = true, Declaration = new CodeType { diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index e50ed0834a..86be33978e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -54,14 +54,14 @@ private void WriteNavigationMetadataConstant(CodeConstant codeElement, LanguageW writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: Record, NavigationMetadata> = {{"); foreach (var navigationMethod in navigationMethods) { - writer.StartBlock($"\"{navigationMethod.Name.ToFirstCharacterLowerCase()}\": {{"); + writer.StartBlock($"{navigationMethod.Name.ToFirstCharacterLowerCase()}: {{"); var requestBuilderName = navigationMethod.ReturnType.Name.ToFirstCharacterUpperCase(); WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName, navigationMethod.Parameters.Where(static x => x.Kind is CodeParameterKind.Path or CodeParameterKind.Custom && !string.IsNullOrEmpty(x.SerializationName)).Select(static x => $"\"{x.SerializationName}\"").ToArray()); writer.CloseBlock("},"); } foreach (var navigationProperty in navigationProperties) { - writer.StartBlock($"\"{navigationProperty.Name.ToFirstCharacterLowerCase()}\": {{"); + writer.StartBlock($"{navigationProperty.Name.ToFirstCharacterLowerCase()}: {{"); var requestBuilderName = navigationProperty.Type.Name.ToFirstCharacterUpperCase(); WriteNavigationMetadataEntry(parentNamespace, writer, requestBuilderName); writer.CloseBlock("},"); @@ -89,14 +89,14 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) .ToArray() is not { Length: > 0 } executorMethods) return; - writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: Record = {{"); + writer.StartBlock($"export const {codeElement.Name.ToFirstCharacterUpperCase()}: RequestsMetadata = {{"); foreach (var executorMethod in executorMethods) { var returnType = conventions.GetTypeString(executorMethod.ReturnType, codeElement); var isVoid = "void".EqualsIgnoreCase(returnType); var isStream = conventions.StreamTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase); var returnTypeWithoutCollectionSymbol = GetReturnTypeWithoutCollectionSymbol(executorMethod, returnType); - writer.StartBlock($"\"{executorMethod.Name.ToFirstCharacterLowerCase()}\": {{"); + writer.StartBlock($"{executorMethod.Name.ToFirstCharacterLowerCase()}: {{"); if (codeClass.Methods.FirstOrDefault(x => x.Kind is CodeMethodKind.RequestGenerator && x.HttpMethod == executorMethod.HttpMethod) is { } generatorMethod && generatorMethod.AcceptHeaderValue is string acceptHeader && !string.IsNullOrEmpty(acceptHeader)) writer.WriteLine($"responseBodyContentType: \"{acceptHeader}\","); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index d2a429b2eb..d8be90451b 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -192,7 +192,7 @@ public void WritesRequestGeneratorBodyForScalar() writer.Write(constant); var result = tw.ToString(); Assert.Contains("export const", result); - Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("RequestsMetadata: RequestsMetadata = {", result); Assert.Contains("responseBodyContentType: \"application/json\"", result); Assert.Contains("requestInformationContentSetMethod: \"setContentFromScalar", result); AssertExtensions.CurlyBracesAreClosed(result); @@ -232,7 +232,7 @@ public void WritesRequestGeneratorBodyForParsable() writer.Write(constant); var result = tw.ToString(); Assert.Contains("export const", result); - Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("RequestsMetadata: RequestsMetadata = {", result); Assert.Contains("responseBodyContentType: \"application/json\"", result); Assert.Contains("requestInformationContentSetMethod: \"setContentFromParsable", result); AssertExtensions.CurlyBracesAreClosed(result); @@ -256,7 +256,7 @@ public void WritesRequestGeneratorBodyKnownRequestBodyType() writer.Write(constant); var result = tw.ToString(); Assert.Contains("export const", result); - Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("RequestsMetadata: RequestsMetadata = {", result); Assert.Contains("requestBodyContentType: \"application/json\"", result); Assert.Contains("requestInformationContentSetMethod: \"setStreamContent", result); AssertExtensions.CurlyBracesAreClosed(result); @@ -289,7 +289,7 @@ public void WritesRequestGeneratorBodyUnknownRequestBodyType() writer.Write(constant); var result = tw.ToString(); Assert.Contains("export const", result); - Assert.Contains("RequestsMetadata: Record = {", result); + Assert.Contains("RequestsMetadata: RequestsMetadata = {", result); Assert.DoesNotContain("requestBodyContentType: \"application/json\"", result); Assert.Contains("requestInformationContentSetMethod: \"setStreamContent", result); Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); From 98eae6c6da91974ac9909409580169e531b1b5ce Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 12 Jan 2024 14:05:35 -0500 Subject: [PATCH 37/40] - uses symbols for error mappings in typescript Signed-off-by: Vincent Biret --- .../Writers/TypeScript/CodeConstantWriter.cs | 8 ++++++-- .../Writers/TypeScript/CodeConstantWriterTests.cs | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs index 86be33978e..5b6d9acad2 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeConstantWriter.cs @@ -80,6 +80,10 @@ private static void WriteNavigationMetadataEntry(CodeNamespace parentNamespace, if (pathParameters is { Length: > 0 }) writer.WriteLine($"pathParametersMappings: [{string.Join(", ", pathParameters)}],"); } + private static string GetErrorMappingKey(string original) => + original.Equals("4XX", StringComparison.OrdinalIgnoreCase) || original.Equals("5XX", StringComparison.OrdinalIgnoreCase) ? + $"_{original.ToUpperInvariant()}" : // to avoid emitting strings that can't be minified + original.ToUpperInvariant(); private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWriter writer) { @@ -105,9 +109,9 @@ private void WriteRequestsMetadataConstant(CodeConstant codeElement, LanguageWri writer.StartBlock("errorMappings: {"); foreach (var errorMapping in executorMethod.ErrorMappings) { - writer.WriteLine($"\"{errorMapping.Key.ToUpperInvariant()}\": {GetFactoryMethodName(errorMapping.Value, codeElement, writer)},"); + writer.WriteLine($"{GetErrorMappingKey(errorMapping.Key)}: {GetFactoryMethodName(errorMapping.Value, codeElement, writer)} as ParsableFactory,"); } - writer.CloseBlock("} as Record>,"); + writer.CloseBlock("},"); } writer.WriteLine($"adapterMethodName: \"{GetSendRequestMethodName(isVoid, isStream, executorMethod.ReturnType.IsCollection, returnTypeWithoutCollectionSymbol)}\","); if (!isVoid) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs index d8be90451b..918f4ca128 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeConstantWriterTests.cs @@ -322,10 +322,9 @@ public void WritesRequestExecutorBody() writer.Write(constant); var result = tw.ToString(); Assert.Contains("errorMappings: {", result); - Assert.Contains("4XX\": createError4XXFromDiscriminatorValue", result); - Assert.Contains("5XX\": createError5XXFromDiscriminatorValue", result); - Assert.Contains("403\": createError403FromDiscriminatorValue", result); - Assert.Contains(" as Record>", result); + Assert.Contains("_4XX: createError4XXFromDiscriminatorValue as ParsableFactory", result); + Assert.Contains("_5XX: createError5XXFromDiscriminatorValue as ParsableFactory", result); + Assert.Contains("403: createError403FromDiscriminatorValue as ParsableFactory", result); } [Fact] public void WritesIndexer() From 306a4cf9de35868f08d40417653989d94a2c545b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 19 Jan 2024 10:05:50 -0500 Subject: [PATCH 38/40] - adds changelog entry for typescript proxy generation --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b16e5896..fd9ae1f14e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fixed a bug where scalar error mappings would be generated even though it's not supported by the http request adapter. [#4018](https://github.com/microsoft/kiota/issues/4018) +- Switched to proxy generation for TypeScript, leading to about ~44% bundle sizes reduction. [#3642](https://github.com/microsoft/kiota/issues/3642) ## [1.10.1] - 2024-01-12 From 8fc91f77bc528dbfe4d23462eef308dacb3daeb2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 19 Jan 2024 11:14:38 -0500 Subject: [PATCH 39/40] - changes client factory prefix to create --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index a6da73b8a8..60272c16d9 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -307,7 +307,7 @@ private static CodeInterface ReplaceRequestBuilderClassByInterface(CodeClass cod if (codeClass.Methods.FirstOrDefault(static x => x.Kind is CodeMethodKind.ClientConstructor) is CodeMethod clientConstructor) { clientConstructor.IsStatic = true; - clientConstructor.Name = $"New{codeClass.Name.ToFirstCharacterUpperCase()}"; + clientConstructor.Name = $"Create{codeClass.Name.ToFirstCharacterUpperCase()}"; codeNamespace.AddFunction(new CodeFunction(clientConstructor)).First().AddUsing(new CodeUsing { @@ -371,7 +371,7 @@ private static void GenerateRequestBuilderCodeFile(CodeInterface codeInterface, .OfType() .ToArray(); - var clientConstructorFunction = codeNamespace.FindChildByName($"New{codeInterface.Name.ToFirstCharacterUpperCase()}", false); + var clientConstructorFunction = codeNamespace.FindChildByName($"Create{codeInterface.Name.ToFirstCharacterUpperCase()}", false); codeNamespace.RemoveChildElement(inlineRequestAndResponseBodyFiles); var elements = new CodeElement[] { codeInterface } From 070eb256701f6b2e45b211cd9cae46d7aa528a2b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 19 Jan 2024 16:22:27 -0500 Subject: [PATCH 40/40] - removes unecessary import Signed-off-by: Vincent Biret --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 60272c16d9..c7ac33287d 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -523,13 +523,8 @@ private static bool HasMultipartBody(CodeMethod m) => new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor && method.Parameters.Any(static y => y.Kind is CodeParameterKind.BackingStore), AbstractionsPackageName, true, "BackingStoreFactory"), - new (static x => x is CodeMethod method && method.Kind is CodeMethodKind.ClientConstructor && - method.Parameters.Any(static y => y.Kind is CodeParameterKind.BackingStore), - AbstractionsPackageName, false, "BackingStoreFactorySingleton"), new (static x => x is CodeProperty prop && prop.Kind is CodePropertyKind.BackingStore, AbstractionsPackageName, true, "BackingStore", "BackedModel"), - new (static x => x is CodeProperty prop && prop.Kind is CodePropertyKind.BackingStore, - AbstractionsPackageName, false, "BackingStoreFactorySingleton"), new (static x => x is CodeMethod m && HasMultipartBody(m), AbstractionsPackageName, MultipartBodyClassName, $"serialize{MultipartBodyClassName}") };