From 4b3cb752f5cdde3a026fc9ec9f4e42f95df80feb Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:07:03 +0300 Subject: [PATCH 1/6] Fix ts codefile linting --- .../TypeScript/CodeFileBlockEndWriter.cs | 17 +++++ .../TypeScript/CodeFileDeclarationWriter.cs | 1 + .../Writers/TypeScript/CodeFunctionWriter.cs | 2 +- .../Writers/TypeScript/TypeScriptWriter.cs | 1 + .../Writers/TypeScript/CodeFileWriterTests.cs | 70 +++++++++++++++++++ .../TypeScript/CodeFunctionWriterTests.cs | 4 +- 6 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs create mode 100644 tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs new file mode 100644 index 0000000000..f047db0190 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs @@ -0,0 +1,17 @@ +using System; +using Kiota.Builder.CodeDOM; +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileBlockEndWriter : BaseElementWriter +{ + public CodeFileBlockEndWriter(TypeScriptConventionService conventionService) : base(conventionService) + { + } + + public override void WriteCodeElement(CodeFileBlockEnd codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + conventions.WriteAutoGeneratedEnd(writer); + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index 5fd774dad6..9debd754f4 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -30,6 +30,7 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW }; } ); + conventions.WriteAutoGeneratedStart(writer); _codeUsingWriter.WriteCodeElement(usings, ns, writer); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 157ba88a85..058f1610a9 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -25,9 +25,9 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w 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"); - conventions.WriteAutoGeneratedStart(writer); if (codeElement.Parent is CodeNamespace) { + conventions.WriteAutoGeneratedStart(writer); _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), writer); } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 11233a0f31..7c02925d6b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,6 +17,7 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName, bool usesBa AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeNameSpaceWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); + AddOrReplaceCodeElementWriter(new CodeFileBlockEndWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService, clientNamespaceName)); } } diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs new file mode 100644 index 0000000000..b166f292b0 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs @@ -0,0 +1,70 @@ +using System; +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 Xunit; + +namespace Kiota.Builder.Tests.Writers.TypeScript; + +public class CodeFileWriterTests +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeNamespace root; + private const string MethodName = "methodName"; + private const string ReturnTypeName = "Somecustomtype"; + + public CodeFileWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + } + + [Fact] + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + + private void WriteCode(LanguageWriter writer, CodeElement element) + { + writer.Write(element); + if (element is not CodeNamespace) + foreach (var childElement in element.GetChildElements() + .Order(new CodeElementOrderComparer())) + { + WriteCode(writer, childElement); + } + + } + + [Fact] + public async Task WritesAutoGenerationStart() + { + var parentClass = TestHelper.CreateModelClass(root, "parentClass", true); + TestHelper.AddSerializationPropertiesToModelClass(parentClass); + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + var codeFile = root.FindChildByName(parentClass.Name.ToFirstCharacterUpperCase()); + WriteCode(writer, codeFile); + + var result = tw.ToString(); + Assert.Contains("// eslint-disable", result); + Assert.Contains("// tslint:disable", result); + Assert.Contains("export function deserializeIntoParentClass", result); + Assert.Contains("export interface ParentClass", result); + Assert.Contains("export function serializeParentClass", result); + Assert.Contains("// eslint-enable", result); + Assert.Contains("// tslint:enable", result); + } + +} diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs index cb45af6c10..8150abc7c1 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFunctionWriterTests.cs @@ -42,8 +42,8 @@ public async Task WritesAutoGenerationStart() var serializeFunction = root.FindChildByName($"deserializeInto{parentClass.Name.ToFirstCharacterUpperCase()}"); writer.Write(serializeFunction); var result = tw.ToString(); - Assert.Contains("// eslint-disable", result); - Assert.Contains("// tslint:disable", result); + Assert.DoesNotContain("// eslint-disable", result); + Assert.DoesNotContain("// tslint:disable", result); } [Fact] public async Task WritesAutoGenerationEnd() From 9e301c23bb99cc5c405352cb7acfb39aabb81e03 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:20:28 +0300 Subject: [PATCH 2/6] Exclude duplicate importS --- .../Writers/TypeScript/CodeFileDeclarationWriter.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index 9debd754f4..4ed056a595 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -31,7 +31,17 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW } ); conventions.WriteAutoGeneratedStart(writer); - _codeUsingWriter.WriteCodeElement(usings, ns, writer); + + // remove duplicate using, keep a single using for each internal type in the same namespace + var enumeratedUsing = usings.ToArray(); + var filteredUsing = enumeratedUsing.Where(x => x.IsExternal) + .Union(enumeratedUsing.ToArray() + .Where(x => x is { IsExternal: false, Declaration.TypeDefinition: not null }) + .GroupBy(x => + $"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name}.{x.Declaration?.Name.ToLowerInvariant()}") + .Select(x => x.First())); + + _codeUsingWriter.WriteCodeElement(filteredUsing, ns, writer); } } From 5cef2499b1ee2b612d886f4447f502dcf7a21288 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:59:43 +0300 Subject: [PATCH 3/6] Sort import order --- .../Writers/TypeScript/CodeFileDeclarationWriter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index 4ed056a595..cbc09ffb01 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -34,12 +34,12 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW // remove duplicate using, keep a single using for each internal type in the same namespace var enumeratedUsing = usings.ToArray(); - var filteredUsing = enumeratedUsing.Where(x => x.IsExternal) + var filteredUsing = enumeratedUsing.Where(static x => x.IsExternal) .Union(enumeratedUsing.ToArray() - .Where(x => x is { IsExternal: false, Declaration.TypeDefinition: not null }) - .GroupBy(x => + .Where(static x => x is { IsExternal: false, Declaration.TypeDefinition: not null }) + .GroupBy(static x => $"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name}.{x.Declaration?.Name.ToLowerInvariant()}") - .Select(x => x.First())); + .Select(static x => x.OrderBy(static x => x.Parent?.Name).First())); _codeUsingWriter.WriteCodeElement(filteredUsing, ns, writer); } From f0080ae447b6f18aea42d8a6f188b74a673af79e Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:36:58 +0300 Subject: [PATCH 4/6] Resolve import statements for typescript with code files --- CHANGELOG.md | 1 + src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 15 +++++++++++---- .../TypeScript/CodeFileDeclarationWriter.cs | 6 ++++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 14 ++++++++------ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f63cb09f6e..713a034679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adds codes files in typescript to reduce number of generated files. [#2116](https://github.com/microsoft/kiota/issues/2116) - Fix null reference exception when a parameter is defined without a schema. (CLI). - Log a message to stderr if a request is skipped due to missing data. (CLI) [#2210](https://github.com/microsoft/kiota/issues/2210) +- Fixes code file generation in typescript [#3419](https://github.com/microsoft/kiota/issues/3419) ## [1.6.1] - 2023-09-11 diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 1b23f70e87..fae34e81a3 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -174,7 +174,7 @@ private void MergeElementsToFile(CodeElement currentElement) private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { - List functions = new List { codeInterface }; + List functions = new List(); foreach (var element in codeNamespace.GetChildElements(true)) { @@ -182,7 +182,7 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames { if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer) && codeFunction.OriginalLocalMethod.Parameters - .Any(x => x?.Type?.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false)) + .Any(x => x.Type.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false)) { functions.Add(codeFunction); } @@ -195,6 +195,10 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames } } + if (!functions.Any()) + return; + + functions.Insert(0, codeInterface); codeNamespace.TryAddCodeFile(codeInterface.Name, functions.ToArray()); } @@ -204,18 +208,21 @@ private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeName .Where(static x => x.IsOfKind(CodeMethodKind.RequestGenerator)) .SelectMany(static x => x.Parameters) .Where(static x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) - .Select(static x => x?.Type?.Name) + .Select(static x => x.Type?.Name) .Where(static x => !string.IsNullOrEmpty(x)) .OfType() .ToList(); + if (!elementNames.Any()) + return; + List configClasses = codeNamespace.FindChildrenByName(elementNames, false) .Where(static x => x != null) .OfType() .ToList(); List queryParamClassNames = configClasses.Where(x => x != null) - .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) + .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type.Name) .Where(static x => !string.IsNullOrEmpty(x)) .OfType() .ToList(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index cbc09ffb01..77f94924b6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -41,6 +41,12 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW $"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name}.{x.Declaration?.Name.ToLowerInvariant()}") .Select(static x => x.OrderBy(static x => x.Parent?.Name).First())); + var array = filteredUsing.ToArray(); + if (!array.Any()) + { + throw new InvalidOperationException($"File missing imports {cf.Name}, name space: {ns.Name}"); + } + _codeUsingWriter.WriteCodeElement(filteredUsing, ns, writer); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 058f1610a9..c41ae3fc9c 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -246,13 +246,15 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur { var resultName = $"create{targetClassName.ToFirstCharacterUpperCase()}FromDiscriminatorValue"; if (conventions.GetTypeString(targetClassType, currentElement, false) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; - if (targetClassType is CodeType currentType && - currentType.TypeDefinition is CodeInterface definitionClass && - definitionClass.GetImmediateParentOfType() is CodeNamespace parentNamespace && - parentNamespace.FindChildByName(resultName) is CodeFunction factoryMethod) + if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) { - var methodName = conventions.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); - return methodName.ToFirstCharacterUpperCase();// static function is aliased + var factoryMethod = definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName) ?? + definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName); + if (factoryMethod != null) + { + var methodName = conventions.GetTypeString(new CodeType { Name = resultName, TypeDefinition = factoryMethod }, currentElement, false); + return methodName.ToFirstCharacterUpperCase();// static function is aliased + } } } throw new InvalidOperationException($"Unable to find factory method for {targetClassType}"); From a8bc004f4e90d2c338f61758ff05b77028b9e5fc Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:45:09 +0300 Subject: [PATCH 5/6] Format code --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 2 +- .../Writers/TypeScript/CodeFileDeclarationWriter.cs | 6 ------ src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index fae34e81a3..674a57ce1d 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -197,7 +197,7 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames if (!functions.Any()) return; - + functions.Insert(0, codeInterface); codeNamespace.TryAddCodeFile(codeInterface.Name, functions.ToArray()); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index 77f94924b6..cbc09ffb01 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -41,12 +41,6 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW $"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name}.{x.Declaration?.Name.ToLowerInvariant()}") .Select(static x => x.OrderBy(static x => x.Parent?.Name).First())); - var array = filteredUsing.ToArray(); - if (!array.Any()) - { - throw new InvalidOperationException($"File missing imports {cf.Name}, name space: {ns.Name}"); - } - _codeUsingWriter.WriteCodeElement(filteredUsing, ns, writer); } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index c41ae3fc9c..fcc2ad4661 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -248,7 +248,7 @@ private string GetFactoryMethodName(CodeTypeBase targetClassType, CodeMethod cur if (conventions.GetTypeString(targetClassType, currentElement, false) is string returnType && targetClassName.EqualsIgnoreCase(returnType)) return resultName; if (targetClassType is CodeType currentType && currentType.TypeDefinition is CodeInterface definitionClass) { - var factoryMethod = definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName) ?? + var factoryMethod = definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName) ?? definitionClass.GetImmediateParentOfType()?.FindChildByName(resultName); if (factoryMethod != null) { From 3b63c937d35255e5695af3236e6e1e385ae2163b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 3 Oct 2023 13:00:02 -0400 Subject: [PATCH 6/6] Update src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs --- .../Writers/TypeScript/CodeFileDeclarationWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index cbc09ffb01..d3069e3828 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -35,7 +35,7 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW // remove duplicate using, keep a single using for each internal type in the same namespace var enumeratedUsing = usings.ToArray(); var filteredUsing = enumeratedUsing.Where(static x => x.IsExternal) - .Union(enumeratedUsing.ToArray() + .Union(enumeratedUsing .Where(static x => x is { IsExternal: false, Declaration.TypeDefinition: not null }) .GroupBy(static x => $"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name}.{x.Declaration?.Name.ToLowerInvariant()}")