From a27ec51d26d3445cb45766690155b244ccd37047 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:10:46 +0300 Subject: [PATCH 01/14] Adds codefiles in typescript --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 10 ++ src/Kiota.Builder/CodeDOM/CodeFile.cs | 13 +- src/Kiota.Builder/CodeDOM/CodeNamespace.cs | 11 +- .../CodeRenderers/TypeScriptCodeRenderer.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 135 ++++++++++++++++++ .../TypeScript/CodeClassDeclarationWriter.cs | 4 +- .../TypeScript/CodeFileBlockEndWriter.cs | 10 ++ .../TypeScript/CodeFileDeclarationWriter.cs | 35 +++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 16 ++- .../CodeInterfaceDeclarationWriter.cs | 4 +- .../Writers/TypeScript/CodeNameSpaceWriter.cs | 1 + .../TypeScriptRelativeImportManager.cs | 11 +- .../Writers/TypeScript/TypeScriptWriter.cs | 2 + .../CodeDOM/CodeNamespaceTests.cs | 13 ++ 14 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index be31a2b068..e59b403ec5 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -122,6 +122,16 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle return Enumerable.Empty(); } + + public IEnumerable FindChildrenByName(IEnumerable childrenName, bool findInChildElements = true) where T : ICodeElement + { + if (childrenName == null) + throw new ArgumentNullException(nameof(childrenName)); + + return childrenName.Where(x => !string.IsNullOrEmpty(x)) + .Select(x => this.FindChildByName(x, findInChildElements)); + } + public T? FindChildByName(string childName, bool findInChildElements = true) where T : ICodeElement { ArgumentException.ThrowIfNullOrEmpty(childName); diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index e304124012..0363187d30 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -17,9 +17,16 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement } public IEnumerable AllUsingsFromChildElements => GetChildElements() - .SelectMany(static x => x.GetChildElements()) - .OfType() - .SelectMany(static x => x.Usings); + .SelectMany(static x => x.GetChildElements()) + .OfType() + .SelectMany(static x => x.Usings); + + public IEnumerable GetChildrenOfType() where T : CodeElement + { + return InnerChildElements.Values.Where(y => y is T) + .Select(x => (x as T)!) + .ToList(); + } } public class CodeFileDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs index de63839750..66f4c3d195 100644 --- a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs +++ b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs @@ -30,8 +30,7 @@ public override string Name StartBlock.Name = name; } } - - public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) + public CodeFile TryAddCodeFileWithChildren(string fileName, CodeElement[] children) { var file = FindChildByName(fileName, false) ?? new CodeFile { Name = fileName }; RemoveChildElement(children); @@ -44,6 +43,11 @@ public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) return file; } + public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) + { + return TryAddCodeFileWithChildren(fileName, children); + } + public bool IsParentOf(CodeNamespace childNamespace) { ArgumentNullException.ThrowIfNull(childNamespace); @@ -70,6 +74,7 @@ public CodeNamespace GetRootNamespace() public IEnumerable Enums => InnerChildElements.Values.OfType(); public IEnumerable Functions => InnerChildElements.Values.OfType(); public IEnumerable Interfaces => InnerChildElements.Values.OfType(); + public IEnumerable Files => InnerChildElements.Values.OfType(); public CodeNamespace? FindNamespaceByName(string nsName) { ArgumentException.ThrowIfNullOrEmpty(nsName); @@ -168,7 +173,7 @@ public NamespaceDifferentialTracker GetDifferential(CodeNamespace importNamespac ArgumentException.ThrowIfNullOrEmpty(namespacePrefix); if (this == importNamespace || Name.Equals(importNamespace.Name, StringComparison.OrdinalIgnoreCase)) // we're in the same namespace return new(); - var prefixLength = namespacePrefix.Length; + var prefixLength = (namespacePrefix.Length > Math.Min(Name.Length, importNamespace.Name.Length) ? 0 : namespacePrefix.Length); var currentNamespaceSegments = Name[prefixLength..] .Split(separator, StringSplitOptions.RemoveEmptyEntries); var importNamespaceSegments = importNamespace diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 21a601e828..853b9fd82e 100644 --- a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs @@ -9,6 +9,6 @@ public TypeScriptCodeRenderer(GenerationConfiguration configuration) : base(conf public override bool ShouldRenderNamespaceFile(CodeNamespace codeNamespace) { if (codeNamespace is null) return false; - return codeNamespace.Interfaces.Any(); + return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(x => x.GetChildrenOfType().Any()).Any(); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 19bda1990a..e822737fb1 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -148,9 +148,144 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance ); IntroducesInterfacesAndFunctions(generatedCode, factoryNameCallbackFromType); AliasUsingsWithSameSymbol(generatedCode); + GenerateCodeFiles(generatedCode); cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } + + private void GenerateCodeFiles(CodeElement currentElement) + { + MergeElementsToFile(currentElement); + CorrectCodeFileUsing(currentElement); + } + + private void MergeElementsToFile(CodeElement currentElement) + { + // create all request builders as a code file with the functions + if (currentElement is CodeInterface codeInterface && codeInterface.IsOfKind(CodeInterfaceKind.Model) && currentElement.Parent is CodeNamespace codeNamespace) + { + GenerateModelCodeFile(codeInterface, codeNamespace); + } + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder) && currentElement.Parent is CodeNamespace namespaceOfRequestBuilder) + { + GenerateRequestBuilderCodeFile(currentClass, namespaceOfRequestBuilder); + } + CrawlTree(currentElement, MergeElementsToFile); + } + + private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) + { + List functions = new List { codeInterface }; + foreach (var element in codeNamespace.GetChildElements(true)) + { + if (element is CodeFunction codeFunction) + { + if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) + { + var exists = codeFunction.OriginalLocalMethod.Parameters + .Where(x => x?.Type?.Name == codeInterface.Name) + .ToList() + .Any(); + + if (exists) + functions.Add(codeFunction); + + } + else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory)) + { + if (codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace)) + functions.Add(codeFunction); + } + } + } + String modelName = codeInterface.Name; + codeNamespace.TryAddCodeFileWithChildren(modelName, functions.ToArray()); + } + + private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) + { + List elementNames = codeClass.Methods + .Where(x => x.IsOfKind(CodeMethodKind.RequestGenerator)) + .SelectMany(x => x.Parameters) + .Where(x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) + .Select(x => x?.Type?.Name) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + List configClasses = codeNamespace.FindChildrenByName(elementNames, false) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + List queryParamClassNames = configClasses.Where(x => x != null) + .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) + .Where(s => s != null) + .Select(s => s!) + .ToList(); + + List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + + List elements = new List { codeClass }; + elements.AddRange(queryParamClasses); + elements.AddRange(configClasses); + + codeNamespace.TryAddCodeFileWithChildren(codeClass.Name, elements.ToArray()); + } + + private static void CorrectCodeFileUsing(CodeElement currentElement) + { + // if element is a code file eliminate using references to the same file + if (currentElement is CodeFile codeFile && codeFile.Parent is CodeNamespace codeNamespace) + { + // correct the using values + // eliminate the using refering the elements in the same file + + HashSet elementSet = codeFile.GetChildElements(true).Select(x => x.Name.ToLowerInvariant()).ToHashSet(); + foreach (var element in codeFile.GetChildElements(true)) + { + var startBlockUsings = element switch + { + CodeFunction f => f.StartBlock.Usings, + CodeInterface ci => ci.Usings, + CodeEnum ce => ce.Usings, + CodeClass cc => cc.Usings, + _ => Enumerable.Empty() + }; + + var foundUsings = startBlockUsings + .Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null) + .Where(y => y.Declaration!.TypeDefinition!.GetImmediateParentOfType() == codeNamespace) + .Where(y => elementSet.Contains(y.Declaration!.TypeDefinition!.Name.ToLowerInvariant())); + + foreach (var x in foundUsings) + { + switch (element) + { + case CodeFunction ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeInterface ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeEnum ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeClass ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + } + } + } + } + + CrawlTree(currentElement, CorrectCodeFileUsing); + } + private static void AliasCollidingSymbols(IEnumerable usings, string currentSymbolName) { var enumeratedUsings = usings.ToArray(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs index a2c86fab5b..56d0de672e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs @@ -17,7 +17,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + + if (codeElement.Parent?.Parent is not CodeFile) + _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); var inheritSymbol = codeElement.Inherits is null ? string.Empty : conventions.GetTypeString(codeElement.Inherits, codeElement); var derivation = (string.IsNullOrEmpty(inheritSymbol) ? string.Empty : $" extends {inheritSymbol}") + diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs new file mode 100644 index 0000000000..be37239450 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs @@ -0,0 +1,10 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileBlockEndWriter : ICodeElementWriter +{ + public void WriteCodeElement(CodeFileBlockEnd codeElement, LanguageWriter writer) + { + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs new file mode 100644 index 0000000000..21b147492f --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileDeclarationWriter : BaseElementWriter +{ + public CodeFileDeclarationWriter(TypeScriptConventionService conventionService) : base(conventionService) { } + + public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + + if (codeElement.Parent is CodeFile cf && cf.Parent is CodeNamespace ns) + { + CodeUsingWriter codeUsingWriter = new(ns.Name); + var usings = cf.GetChildElements().SelectMany(static x => + { + var startBlockUsings = x switch + { + CodeFunction f => f.StartBlock.Usings, + CodeInterface ci => ci.Usings, + CodeClass cc => cc.Usings, + _ => Enumerable.Empty() + }; + return startBlockUsings; + } + ); + codeUsingWriter.WriteCodeElement(usings, ns, writer); + } + } + +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 5a7c0a2eaa..21d5931a40 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -22,8 +22,11 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w ArgumentNullException.ThrowIfNull(codeElement); if (codeElement.OriginalLocalMethod == null) throw new InvalidOperationException($"{nameof(codeElement.OriginalLocalMethod)} should not be null"); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is not CodeNamespace) throw new InvalidOperationException("the parent of a function should be a namespace"); - _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), 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) + _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), writer); + var codeMethod = codeElement.OriginalLocalMethod; var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty; @@ -85,11 +88,12 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private string getDeserializationFunction(CodeElement codeElement, string returnType) { - if (codeElement.Parent is not CodeNamespace codeNamespace) + var parent = codeElement.Parent switch { - throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace"); - } - var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}"); + CodeNamespace codeNamespace => codeNamespace.FindChildByName($"deserializeInto{returnType}"), + CodeFile codeFile => codeFile.FindChildByName($"deserializeInto{returnType}"), + _ => throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace or file") + }; return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs index bb938fd1a1..c2f3f1be0e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs @@ -20,7 +20,9 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language ArgumentNullException.ThrowIfNull(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + + if (codeElement.Parent?.Parent is not CodeFile) + _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); var derivation = codeElement.Implements.Any() ? $" extends {codeElement.Implements.Select(static x => x.Name).Aggregate(static (x, y) => x + ", " + y)}" : string.Empty; writer.StartBlock($"export interface {codeElement.Name.ToFirstCharacterUpperCase()}{derivation} {{"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs index 5838313427..7fc5707858 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs @@ -21,6 +21,7 @@ public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer.WriteLines(codeElement.Enums .Concat(codeElement.Functions) .Concat(codeElement.Interfaces) + .Concat(codeElement.Files) .OrderBy(static x => x is CodeEnum ? 0 : 1) .ThenBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) .Select(static x => x.Name.ToFirstCharacterLowerCase()) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs index 10cf9c8987..906ad47ae8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs @@ -38,7 +38,16 @@ public override (string, string, string) GetRelativeImportPathForUsing(CodeUsing importPath += codeUsing.Name; else if (!isCodeUsingAModel) { - importPath += (!string.IsNullOrEmpty(codeUsing.Declaration?.TypeDefinition?.Name) ? codeUsing.Declaration.TypeDefinition.Name : codeUsing.Declaration?.Name).ToFirstCharacterLowerCase(); + var typeParent = codeUsing.Declaration?.TypeDefinition?.GetImmediateParentOfType(); + var nameSpaceElement = typeParent?.FindChildByName(codeUsing.Declaration?.Name ?? codeUsing.Name); + if (nameSpaceElement?.Parent is CodeFile f) + { + importPath += f.Name.ToFirstCharacterLowerCase(); + } + else + { + importPath += (!string.IsNullOrEmpty(codeUsing.Declaration?.TypeDefinition?.Name) ? codeUsing.Declaration.TypeDefinition.Name : codeUsing.Declaration?.Name).ToFirstCharacterLowerCase(); + } } return (importSymbol, codeUsing.Alias, importPath); } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index e63a6174eb..be0304e68b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,5 +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()); + AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService)); } } diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs index 7130b293d5..b4eaf8e1c2 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs @@ -138,4 +138,17 @@ public void IsParentOf() Assert.True(child.IsParentOf(grandchild)); Assert.False(grandchild.IsParentOf(child)); } + + [Fact] + public void GetDifferential() + { + + var root = CodeNamespace.InitRootNamespace(); + root.Name = "Nyt.model.item"; + var child1 = root.AddNamespace("Nyt.model.item"); + var child2 = root.AddNamespace("Nyt.model"); + + + child1.GetDifferential(child2, child1.Name, '.'); + } } From 154de67c66b8a5727015da7ab3e10c4b1dba9938 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:59:15 +0300 Subject: [PATCH 02/14] Adds unit tests --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 2 +- src/Kiota.Builder/CodeDOM/CodeFile.cs | 2 +- .../CodeRenderers/TypeScriptCodeRenderer.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 30 +++-- .../TypeScriptLanguageRefinerTests.cs | 118 ++++++++++++++++++ 5 files changed, 135 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index e59b403ec5..8931e9d2fd 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -128,7 +128,7 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle if (childrenName == null) throw new ArgumentNullException(nameof(childrenName)); - return childrenName.Where(x => !string.IsNullOrEmpty(x)) + return childrenName.Where(static x => !string.IsNullOrEmpty(x)) .Select(x => this.FindChildByName(x, findInChildElements)); } diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 0363187d30..a961abf1b2 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -24,7 +24,7 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement public IEnumerable GetChildrenOfType() where T : CodeElement { return InnerChildElements.Values.Where(y => y is T) - .Select(x => (x as T)!) + .Select(static x => (x as T)!) .ToList(); } } diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 853b9fd82e..28526f06e2 100644 --- a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs @@ -9,6 +9,6 @@ public TypeScriptCodeRenderer(GenerationConfiguration configuration) : base(conf public override bool ShouldRenderNamespaceFile(CodeNamespace codeNamespace) { if (codeNamespace is null) return false; - return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(x => x.GetChildrenOfType().Any()).Any(); + return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(static x => x.GetChildrenOfType().Any()).Any(); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index e822737fb1..3f2df610f5 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -176,6 +176,7 @@ private void MergeElementsToFile(CodeElement currentElement) private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { List functions = new List { codeInterface }; + foreach (var element in codeNamespace.GetChildElements(true)) { if (element is CodeFunction codeFunction) @@ -183,9 +184,7 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) { var exists = codeFunction.OriginalLocalMethod.Parameters - .Where(x => x?.Type?.Name == codeInterface.Name) - .ToList() - .Any(); + .Any(x => x?.Type?.Name == codeInterface.Name); if (exists) functions.Add(codeFunction); @@ -205,31 +204,30 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) { List elementNames = codeClass.Methods - .Where(x => x.IsOfKind(CodeMethodKind.RequestGenerator)) - .SelectMany(x => x.Parameters) - .Where(x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) - .Select(x => x?.Type?.Name) - .Where(x => x != null) - .Select(x => x!) + .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) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); List configClasses = codeNamespace.FindChildrenByName(elementNames, false) - .Where(x => x != null) - .Select(x => x!) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); List queryParamClassNames = configClasses.Where(x => x != null) .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) - .Where(s => s != null) - .Select(s => s!) + .Where(static s => s != null) + .Select(static s => s!) .ToList(); List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) - .Where(x => x != null) - .Select(x => x!) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); - List elements = new List { codeClass }; elements.AddRange(queryParamClasses); elements.AddRange(configClasses); diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 53c2c005c1..3b18252157 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -637,5 +637,123 @@ public async Task ReplaceRequestConfigsQueryParams() Assert.DoesNotContain(testNS.Classes, x => x.Name == "requestConfig"); Assert.DoesNotContain(testNS.Classes, x => x.Name == "queryParams"); } + + + [Fact] + public async Task GeneratesCodeFiles() + { + var model = TestHelper.CreateModelClass(root); + + model.AddMethod(new CodeMethod + { + Name = "factory", + Kind = CodeMethodKind.Factory, + IsAsync = false, + IsStatic = true, + ReturnType = new CodeType + { + Name = "void", + TypeDefinition = model + }, + }); ; + model.AddProperty(new CodeProperty + { + Name = "core", + Kind = CodePropertyKind.RequestAdapter, + Type = new CodeType + { + Name = HttpCoreDefaultName + } + }, new() + { + Name = "someDate", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = DateTimeOffsetDefaultName, + } + }, new() + { + Name = "additionalData", + Kind = CodePropertyKind.AdditionalData, + Type = new CodeType + { + Name = AdditionalDataDefaultName + } + }, new() + { + Name = "pathParameters", + Kind = CodePropertyKind.PathParameters, + Type = new CodeType + { + Name = PathParametersDefaultName + }, + DefaultValue = PathParametersDefaultValue + }); + var executorMethod = model.AddMethod(new CodeMethod + { + Name = "executor", + Kind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType + { + Name = "string" + } + }).First(); + const string serializerDefaultName = "ISerializationWriter"; + var serializationMethod = model.AddMethod(new CodeMethod + { + Name = "seriailization", + Kind = CodeMethodKind.Serializer, + ReturnType = new CodeType + { + Name = "string" + } + }).First(); + serializationMethod.AddParameter(new CodeParameter + { + Name = serializerDefaultName, + Kind = CodeParameterKind.Serializer, + Type = new CodeType + { + Name = serializerDefaultName, + } + }); + var constructorMethod = model.AddMethod(new CodeMethod + { + Name = "constructor", + Kind = CodeMethodKind.Constructor, + ReturnType = new CodeType + { + Name = "void" + } + }).First(); + constructorMethod.AddParameter(new CodeParameter + { + Name = "pathParameters", + Kind = CodeParameterKind.PathParameters, + Type = new CodeType + { + Name = PathParametersDefaultName + }, + }); + + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + Assert.NotNull(codeFile); // codefile exists + + // model , interface, deserializer, serializer should be direct descendant of the codefile + Assert.NotNull(codeFile.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(codeFile.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(codeFile.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); + Assert.NotNull(codeFile.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); + + // model , interface, deserializer, serializer should be a direct descendant of the namespace + Assert.NotNull(root.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(root.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(root.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); + Assert.NotNull(root.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); + + } #endregion } From ffcc816332a735f5fc7837003c7febeeae463127 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:25:54 +0300 Subject: [PATCH 03/14] Reformat code --- .../Refiners/TypeScriptLanguageRefinerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 3b18252157..a0ff8d116c 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -741,7 +741,7 @@ public async Task GeneratesCodeFiles() var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); Assert.NotNull(codeFile); // codefile exists - + // model , interface, deserializer, serializer should be direct descendant of the codefile Assert.NotNull(codeFile.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); Assert.NotNull(codeFile.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); From f924b89d93fd58bb96d1c94d94e84d7bf0da0b7c Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:10:46 +0300 Subject: [PATCH 04/14] Adds codefiles in typescript --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 10 ++ src/Kiota.Builder/CodeDOM/CodeFile.cs | 13 +- src/Kiota.Builder/CodeDOM/CodeNamespace.cs | 11 +- .../CodeRenderers/TypeScriptCodeRenderer.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 135 ++++++++++++++++++ .../TypeScript/CodeClassDeclarationWriter.cs | 4 +- .../TypeScript/CodeFileBlockEndWriter.cs | 10 ++ .../TypeScript/CodeFileDeclarationWriter.cs | 35 +++++ .../Writers/TypeScript/CodeFunctionWriter.cs | 16 ++- .../CodeInterfaceDeclarationWriter.cs | 4 +- .../Writers/TypeScript/CodeNameSpaceWriter.cs | 1 + .../TypeScriptRelativeImportManager.cs | 11 +- .../Writers/TypeScript/TypeScriptWriter.cs | 2 + .../CodeDOM/CodeNamespaceTests.cs | 13 ++ 14 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs create mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index be31a2b068..e59b403ec5 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -122,6 +122,16 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle return Enumerable.Empty(); } + + public IEnumerable FindChildrenByName(IEnumerable childrenName, bool findInChildElements = true) where T : ICodeElement + { + if (childrenName == null) + throw new ArgumentNullException(nameof(childrenName)); + + return childrenName.Where(x => !string.IsNullOrEmpty(x)) + .Select(x => this.FindChildByName(x, findInChildElements)); + } + public T? FindChildByName(string childName, bool findInChildElements = true) where T : ICodeElement { ArgumentException.ThrowIfNullOrEmpty(childName); diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index e304124012..0363187d30 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -17,9 +17,16 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement } public IEnumerable AllUsingsFromChildElements => GetChildElements() - .SelectMany(static x => x.GetChildElements()) - .OfType() - .SelectMany(static x => x.Usings); + .SelectMany(static x => x.GetChildElements()) + .OfType() + .SelectMany(static x => x.Usings); + + public IEnumerable GetChildrenOfType() where T : CodeElement + { + return InnerChildElements.Values.Where(y => y is T) + .Select(x => (x as T)!) + .ToList(); + } } public class CodeFileDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs index de63839750..66f4c3d195 100644 --- a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs +++ b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs @@ -30,8 +30,7 @@ public override string Name StartBlock.Name = name; } } - - public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) + public CodeFile TryAddCodeFileWithChildren(string fileName, CodeElement[] children) { var file = FindChildByName(fileName, false) ?? new CodeFile { Name = fileName }; RemoveChildElement(children); @@ -44,6 +43,11 @@ public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) return file; } + public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) + { + return TryAddCodeFileWithChildren(fileName, children); + } + public bool IsParentOf(CodeNamespace childNamespace) { ArgumentNullException.ThrowIfNull(childNamespace); @@ -70,6 +74,7 @@ public CodeNamespace GetRootNamespace() public IEnumerable Enums => InnerChildElements.Values.OfType(); public IEnumerable Functions => InnerChildElements.Values.OfType(); public IEnumerable Interfaces => InnerChildElements.Values.OfType(); + public IEnumerable Files => InnerChildElements.Values.OfType(); public CodeNamespace? FindNamespaceByName(string nsName) { ArgumentException.ThrowIfNullOrEmpty(nsName); @@ -168,7 +173,7 @@ public NamespaceDifferentialTracker GetDifferential(CodeNamespace importNamespac ArgumentException.ThrowIfNullOrEmpty(namespacePrefix); if (this == importNamespace || Name.Equals(importNamespace.Name, StringComparison.OrdinalIgnoreCase)) // we're in the same namespace return new(); - var prefixLength = namespacePrefix.Length; + var prefixLength = (namespacePrefix.Length > Math.Min(Name.Length, importNamespace.Name.Length) ? 0 : namespacePrefix.Length); var currentNamespaceSegments = Name[prefixLength..] .Split(separator, StringSplitOptions.RemoveEmptyEntries); var importNamespaceSegments = importNamespace diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 21a601e828..853b9fd82e 100644 --- a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs @@ -9,6 +9,6 @@ public TypeScriptCodeRenderer(GenerationConfiguration configuration) : base(conf public override bool ShouldRenderNamespaceFile(CodeNamespace codeNamespace) { if (codeNamespace is null) return false; - return codeNamespace.Interfaces.Any(); + return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(x => x.GetChildrenOfType().Any()).Any(); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 19bda1990a..e822737fb1 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -148,9 +148,144 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance ); IntroducesInterfacesAndFunctions(generatedCode, factoryNameCallbackFromType); AliasUsingsWithSameSymbol(generatedCode); + GenerateCodeFiles(generatedCode); cancellationToken.ThrowIfCancellationRequested(); }, cancellationToken); } + + private void GenerateCodeFiles(CodeElement currentElement) + { + MergeElementsToFile(currentElement); + CorrectCodeFileUsing(currentElement); + } + + private void MergeElementsToFile(CodeElement currentElement) + { + // create all request builders as a code file with the functions + if (currentElement is CodeInterface codeInterface && codeInterface.IsOfKind(CodeInterfaceKind.Model) && currentElement.Parent is CodeNamespace codeNamespace) + { + GenerateModelCodeFile(codeInterface, codeNamespace); + } + if (currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.RequestBuilder) && currentElement.Parent is CodeNamespace namespaceOfRequestBuilder) + { + GenerateRequestBuilderCodeFile(currentClass, namespaceOfRequestBuilder); + } + CrawlTree(currentElement, MergeElementsToFile); + } + + private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) + { + List functions = new List { codeInterface }; + foreach (var element in codeNamespace.GetChildElements(true)) + { + if (element is CodeFunction codeFunction) + { + if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) + { + var exists = codeFunction.OriginalLocalMethod.Parameters + .Where(x => x?.Type?.Name == codeInterface.Name) + .ToList() + .Any(); + + if (exists) + functions.Add(codeFunction); + + } + else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory)) + { + if (codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace)) + functions.Add(codeFunction); + } + } + } + String modelName = codeInterface.Name; + codeNamespace.TryAddCodeFileWithChildren(modelName, functions.ToArray()); + } + + private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) + { + List elementNames = codeClass.Methods + .Where(x => x.IsOfKind(CodeMethodKind.RequestGenerator)) + .SelectMany(x => x.Parameters) + .Where(x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) + .Select(x => x?.Type?.Name) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + List configClasses = codeNamespace.FindChildrenByName(elementNames, false) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + List queryParamClassNames = configClasses.Where(x => x != null) + .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) + .Where(s => s != null) + .Select(s => s!) + .ToList(); + + List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + + List elements = new List { codeClass }; + elements.AddRange(queryParamClasses); + elements.AddRange(configClasses); + + codeNamespace.TryAddCodeFileWithChildren(codeClass.Name, elements.ToArray()); + } + + private static void CorrectCodeFileUsing(CodeElement currentElement) + { + // if element is a code file eliminate using references to the same file + if (currentElement is CodeFile codeFile && codeFile.Parent is CodeNamespace codeNamespace) + { + // correct the using values + // eliminate the using refering the elements in the same file + + HashSet elementSet = codeFile.GetChildElements(true).Select(x => x.Name.ToLowerInvariant()).ToHashSet(); + foreach (var element in codeFile.GetChildElements(true)) + { + var startBlockUsings = element switch + { + CodeFunction f => f.StartBlock.Usings, + CodeInterface ci => ci.Usings, + CodeEnum ce => ce.Usings, + CodeClass cc => cc.Usings, + _ => Enumerable.Empty() + }; + + var foundUsings = startBlockUsings + .Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null) + .Where(y => y.Declaration!.TypeDefinition!.GetImmediateParentOfType() == codeNamespace) + .Where(y => elementSet.Contains(y.Declaration!.TypeDefinition!.Name.ToLowerInvariant())); + + foreach (var x in foundUsings) + { + switch (element) + { + case CodeFunction ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeInterface ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeEnum ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + case CodeClass ci: + ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + break; + } + } + } + } + + CrawlTree(currentElement, CorrectCodeFileUsing); + } + private static void AliasCollidingSymbols(IEnumerable usings, string currentSymbolName) { var enumeratedUsings = usings.ToArray(); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs index a2c86fab5b..56d0de672e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs @@ -17,7 +17,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + + if (codeElement.Parent?.Parent is not CodeFile) + _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); var inheritSymbol = codeElement.Inherits is null ? string.Empty : conventions.GetTypeString(codeElement.Inherits, codeElement); var derivation = (string.IsNullOrEmpty(inheritSymbol) ? string.Empty : $" extends {inheritSymbol}") + diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs new file mode 100644 index 0000000000..be37239450 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs @@ -0,0 +1,10 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileBlockEndWriter : ICodeElementWriter +{ + public void WriteCodeElement(CodeFileBlockEnd codeElement, LanguageWriter writer) + { + } +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs new file mode 100644 index 0000000000..21b147492f --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileDeclarationWriter : BaseElementWriter +{ + public CodeFileDeclarationWriter(TypeScriptConventionService conventionService) : base(conventionService) { } + + public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + + if (codeElement.Parent is CodeFile cf && cf.Parent is CodeNamespace ns) + { + CodeUsingWriter codeUsingWriter = new(ns.Name); + var usings = cf.GetChildElements().SelectMany(static x => + { + var startBlockUsings = x switch + { + CodeFunction f => f.StartBlock.Usings, + CodeInterface ci => ci.Usings, + CodeClass cc => cc.Usings, + _ => Enumerable.Empty() + }; + return startBlockUsings; + } + ); + codeUsingWriter.WriteCodeElement(usings, ns, writer); + } + } + +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 5a7c0a2eaa..21d5931a40 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -22,8 +22,11 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w ArgumentNullException.ThrowIfNull(codeElement); if (codeElement.OriginalLocalMethod == null) throw new InvalidOperationException($"{nameof(codeElement.OriginalLocalMethod)} should not be null"); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is not CodeNamespace) throw new InvalidOperationException("the parent of a function should be a namespace"); - _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), 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) + _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), writer); + var codeMethod = codeElement.OriginalLocalMethod; var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty; @@ -85,11 +88,12 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private string getDeserializationFunction(CodeElement codeElement, string returnType) { - if (codeElement.Parent is not CodeNamespace codeNamespace) + var parent = codeElement.Parent switch { - throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace"); - } - var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}"); + CodeNamespace codeNamespace => codeNamespace.FindChildByName($"deserializeInto{returnType}"), + CodeFile codeFile => codeFile.FindChildByName($"deserializeInto{returnType}"), + _ => throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace or file") + }; return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs index bb938fd1a1..c2f3f1be0e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs @@ -20,7 +20,9 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language ArgumentNullException.ThrowIfNull(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + + if (codeElement.Parent?.Parent is not CodeFile) + _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); var derivation = codeElement.Implements.Any() ? $" extends {codeElement.Implements.Select(static x => x.Name).Aggregate(static (x, y) => x + ", " + y)}" : string.Empty; writer.StartBlock($"export interface {codeElement.Name.ToFirstCharacterUpperCase()}{derivation} {{"); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs index 5838313427..7fc5707858 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs @@ -21,6 +21,7 @@ public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer.WriteLines(codeElement.Enums .Concat(codeElement.Functions) .Concat(codeElement.Interfaces) + .Concat(codeElement.Files) .OrderBy(static x => x is CodeEnum ? 0 : 1) .ThenBy(static x => x.Name, StringComparer.OrdinalIgnoreCase) .Select(static x => x.Name.ToFirstCharacterLowerCase()) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs index 10cf9c8987..906ad47ae8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs @@ -38,7 +38,16 @@ public override (string, string, string) GetRelativeImportPathForUsing(CodeUsing importPath += codeUsing.Name; else if (!isCodeUsingAModel) { - importPath += (!string.IsNullOrEmpty(codeUsing.Declaration?.TypeDefinition?.Name) ? codeUsing.Declaration.TypeDefinition.Name : codeUsing.Declaration?.Name).ToFirstCharacterLowerCase(); + var typeParent = codeUsing.Declaration?.TypeDefinition?.GetImmediateParentOfType(); + var nameSpaceElement = typeParent?.FindChildByName(codeUsing.Declaration?.Name ?? codeUsing.Name); + if (nameSpaceElement?.Parent is CodeFile f) + { + importPath += f.Name.ToFirstCharacterLowerCase(); + } + else + { + importPath += (!string.IsNullOrEmpty(codeUsing.Declaration?.TypeDefinition?.Name) ? codeUsing.Declaration.TypeDefinition.Name : codeUsing.Declaration?.Name).ToFirstCharacterLowerCase(); + } } return (importSymbol, codeUsing.Alias, importPath); } diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index e63a6174eb..be0304e68b 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,5 +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()); + AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService)); } } diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs index 7130b293d5..b4eaf8e1c2 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs @@ -138,4 +138,17 @@ public void IsParentOf() Assert.True(child.IsParentOf(grandchild)); Assert.False(grandchild.IsParentOf(child)); } + + [Fact] + public void GetDifferential() + { + + var root = CodeNamespace.InitRootNamespace(); + root.Name = "Nyt.model.item"; + var child1 = root.AddNamespace("Nyt.model.item"); + var child2 = root.AddNamespace("Nyt.model"); + + + child1.GetDifferential(child2, child1.Name, '.'); + } } From 5f8b3caeea215d02dd2d795028c25eabaae728fe Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:59:15 +0300 Subject: [PATCH 05/14] Adds unit tests --- src/Kiota.Builder/CodeDOM/CodeBlock.cs | 2 +- src/Kiota.Builder/CodeDOM/CodeFile.cs | 2 +- .../CodeRenderers/TypeScriptCodeRenderer.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 30 +++-- .../TypeScriptLanguageRefinerTests.cs | 118 ++++++++++++++++++ 5 files changed, 135 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index e59b403ec5..8931e9d2fd 100644 --- a/src/Kiota.Builder/CodeDOM/CodeBlock.cs +++ b/src/Kiota.Builder/CodeDOM/CodeBlock.cs @@ -128,7 +128,7 @@ public IEnumerable FindChildrenByName(string childName) where T : ICodeEle if (childrenName == null) throw new ArgumentNullException(nameof(childrenName)); - return childrenName.Where(x => !string.IsNullOrEmpty(x)) + return childrenName.Where(static x => !string.IsNullOrEmpty(x)) .Select(x => this.FindChildByName(x, findInChildElements)); } diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 0363187d30..a961abf1b2 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -24,7 +24,7 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement public IEnumerable GetChildrenOfType() where T : CodeElement { return InnerChildElements.Values.Where(y => y is T) - .Select(x => (x as T)!) + .Select(static x => (x as T)!) .ToList(); } } diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 853b9fd82e..28526f06e2 100644 --- a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs @@ -9,6 +9,6 @@ public TypeScriptCodeRenderer(GenerationConfiguration configuration) : base(conf public override bool ShouldRenderNamespaceFile(CodeNamespace codeNamespace) { if (codeNamespace is null) return false; - return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(x => x.GetChildrenOfType().Any()).Any(); + return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(static x => x.GetChildrenOfType().Any()).Any(); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index e822737fb1..3f2df610f5 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -176,6 +176,7 @@ private void MergeElementsToFile(CodeElement currentElement) private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNamespace codeNamespace) { List functions = new List { codeInterface }; + foreach (var element in codeNamespace.GetChildElements(true)) { if (element is CodeFunction codeFunction) @@ -183,9 +184,7 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) { var exists = codeFunction.OriginalLocalMethod.Parameters - .Where(x => x?.Type?.Name == codeInterface.Name) - .ToList() - .Any(); + .Any(x => x?.Type?.Name == codeInterface.Name); if (exists) functions.Add(codeFunction); @@ -205,31 +204,30 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) { List elementNames = codeClass.Methods - .Where(x => x.IsOfKind(CodeMethodKind.RequestGenerator)) - .SelectMany(x => x.Parameters) - .Where(x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) - .Select(x => x?.Type?.Name) - .Where(x => x != null) - .Select(x => x!) + .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) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); List configClasses = codeNamespace.FindChildrenByName(elementNames, false) - .Where(x => x != null) - .Select(x => x!) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); List queryParamClassNames = configClasses.Where(x => x != null) .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) - .Where(s => s != null) - .Select(s => s!) + .Where(static s => s != null) + .Select(static s => s!) .ToList(); List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) - .Where(x => x != null) - .Select(x => x!) + .Where(static x => x != null) + .Select(static x => x!) .ToList(); - List elements = new List { codeClass }; elements.AddRange(queryParamClasses); elements.AddRange(configClasses); diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 53c2c005c1..3b18252157 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -637,5 +637,123 @@ public async Task ReplaceRequestConfigsQueryParams() Assert.DoesNotContain(testNS.Classes, x => x.Name == "requestConfig"); Assert.DoesNotContain(testNS.Classes, x => x.Name == "queryParams"); } + + + [Fact] + public async Task GeneratesCodeFiles() + { + var model = TestHelper.CreateModelClass(root); + + model.AddMethod(new CodeMethod + { + Name = "factory", + Kind = CodeMethodKind.Factory, + IsAsync = false, + IsStatic = true, + ReturnType = new CodeType + { + Name = "void", + TypeDefinition = model + }, + }); ; + model.AddProperty(new CodeProperty + { + Name = "core", + Kind = CodePropertyKind.RequestAdapter, + Type = new CodeType + { + Name = HttpCoreDefaultName + } + }, new() + { + Name = "someDate", + Kind = CodePropertyKind.Custom, + Type = new CodeType + { + Name = DateTimeOffsetDefaultName, + } + }, new() + { + Name = "additionalData", + Kind = CodePropertyKind.AdditionalData, + Type = new CodeType + { + Name = AdditionalDataDefaultName + } + }, new() + { + Name = "pathParameters", + Kind = CodePropertyKind.PathParameters, + Type = new CodeType + { + Name = PathParametersDefaultName + }, + DefaultValue = PathParametersDefaultValue + }); + var executorMethod = model.AddMethod(new CodeMethod + { + Name = "executor", + Kind = CodeMethodKind.RequestExecutor, + ReturnType = new CodeType + { + Name = "string" + } + }).First(); + const string serializerDefaultName = "ISerializationWriter"; + var serializationMethod = model.AddMethod(new CodeMethod + { + Name = "seriailization", + Kind = CodeMethodKind.Serializer, + ReturnType = new CodeType + { + Name = "string" + } + }).First(); + serializationMethod.AddParameter(new CodeParameter + { + Name = serializerDefaultName, + Kind = CodeParameterKind.Serializer, + Type = new CodeType + { + Name = serializerDefaultName, + } + }); + var constructorMethod = model.AddMethod(new CodeMethod + { + Name = "constructor", + Kind = CodeMethodKind.Constructor, + ReturnType = new CodeType + { + Name = "void" + } + }).First(); + constructorMethod.AddParameter(new CodeParameter + { + Name = "pathParameters", + Kind = CodeParameterKind.PathParameters, + Type = new CodeType + { + Name = PathParametersDefaultName + }, + }); + + await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); + + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + Assert.NotNull(codeFile); // codefile exists + + // model , interface, deserializer, serializer should be direct descendant of the codefile + Assert.NotNull(codeFile.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(codeFile.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(codeFile.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); + Assert.NotNull(codeFile.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); + + // model , interface, deserializer, serializer should be a direct descendant of the namespace + Assert.NotNull(root.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(root.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.NotNull(root.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); + Assert.NotNull(root.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); + + } #endregion } From e682053019256fe42f60fde1401949c8f4e72668 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:25:54 +0300 Subject: [PATCH 06/14] Reformat code --- .../Refiners/TypeScriptLanguageRefinerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 3b18252157..a0ff8d116c 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -741,7 +741,7 @@ public async Task GeneratesCodeFiles() var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); Assert.NotNull(codeFile); // codefile exists - + // model , interface, deserializer, serializer should be direct descendant of the codefile Assert.NotNull(codeFile.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); Assert.NotNull(codeFile.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); From 29abce71264cd9bd9935869860308a1d0c663a87 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:06:23 +0300 Subject: [PATCH 07/14] Refactor codefiles --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 8 +------- src/Kiota.Builder/CodeDOM/CodeNamespace.cs | 8 ++------ .../CodeRenderers/TypeScriptCodeRenderer.cs | 2 +- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 4 ++-- .../Writers/TypeScript/CodeFileBlockEndWriter.cs | 10 ---------- .../Writers/TypeScript/TypeScriptWriter.cs | 1 - 6 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index a961abf1b2..c9223cc273 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,6 +6,7 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { + public IEnumerable Interfaces => InnerChildElements.Values.OfType(); public IEnumerable AddElements(params T[] elements) where T : CodeElement { if (elements == null || elements.Any(static x => x == null)) @@ -20,13 +21,6 @@ public IEnumerable AddElements(params T[] elements) where T : CodeElement .SelectMany(static x => x.GetChildElements()) .OfType() .SelectMany(static x => x.Usings); - - public IEnumerable GetChildrenOfType() where T : CodeElement - { - return InnerChildElements.Values.Where(y => y is T) - .Select(static x => (x as T)!) - .ToList(); - } } public class CodeFileDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs index 66f4c3d195..e4776d6149 100644 --- a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs +++ b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs @@ -30,7 +30,8 @@ public override string Name StartBlock.Name = name; } } - public CodeFile TryAddCodeFileWithChildren(string fileName, CodeElement[] children) + + public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) { var file = FindChildByName(fileName, false) ?? new CodeFile { Name = fileName }; RemoveChildElement(children); @@ -43,11 +44,6 @@ public CodeFile TryAddCodeFileWithChildren(string fileName, CodeElement[] childr return file; } - public CodeFile TryAddCodeFile(string fileName, params CodeElement[] children) - { - return TryAddCodeFileWithChildren(fileName, children); - } - public bool IsParentOf(CodeNamespace childNamespace) { ArgumentNullException.ThrowIfNull(childNamespace); diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 28526f06e2..ce653ecb0f 100644 --- a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs @@ -9,6 +9,6 @@ public TypeScriptCodeRenderer(GenerationConfiguration configuration) : base(conf public override bool ShouldRenderNamespaceFile(CodeNamespace codeNamespace) { if (codeNamespace is null) return false; - return codeNamespace.Interfaces.Any() || codeNamespace.Files.Where(static x => x.GetChildrenOfType().Any()).Any(); + return codeNamespace.Interfaces.Any() || codeNamespace.Files.Any(static x => x.Interfaces.Any()); } } diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 3f2df610f5..a933b31189 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -198,7 +198,7 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames } } String modelName = codeInterface.Name; - codeNamespace.TryAddCodeFileWithChildren(modelName, functions.ToArray()); + codeNamespace.TryAddCodeFile(modelName, functions.ToArray()); } private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) @@ -232,7 +232,7 @@ private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeName elements.AddRange(queryParamClasses); elements.AddRange(configClasses); - codeNamespace.TryAddCodeFileWithChildren(codeClass.Name, elements.ToArray()); + codeNamespace.TryAddCodeFile(codeClass.Name, elements.ToArray()); } private static void CorrectCodeFileUsing(CodeElement currentElement) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs deleted file mode 100644 index be37239450..0000000000 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileBlockEndWriter.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.TypeScript; - -public class CodeFileBlockEndWriter : ICodeElementWriter -{ - public void WriteCodeElement(CodeFileBlockEnd codeElement, LanguageWriter writer) - { - } -} diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index be0304e68b..aea7e1a7e3 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,7 +17,6 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName, bool usesBa AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeNameSpaceWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); - AddOrReplaceCodeElementWriter(new CodeFileBlockEndWriter()); AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService)); } } From 792defceaa80331fd2c0e2b577d35ec8de958962 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:26:59 +0300 Subject: [PATCH 08/14] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b15d0d68d2..38c5ea33e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Localhost based descriptions are not cached anymore to facilitate development workflows. [#3316](https://github.com/microsoft/kiota/issues/3316) +- Adds codes files in typescript to reduce number of generated files. [#2116](https://github.com/microsoft/kiota/issues/2116) + ## [1.6.1] - 2023-09-11 From 727a41421cf957201444d5579dabb03e2c2621d6 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:33:36 +0300 Subject: [PATCH 09/14] Fix unit tests --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 5 ++ src/Kiota.Builder/CodeDOM/CodeNamespace.cs | 2 +- .../Refiners/TypeScriptRefiner.cs | 21 ++++---- .../TypeScript/CodeClassDeclarationWriter.cs | 2 +- .../TypeScript/CodeFileDeclarationWriter.cs | 17 ++++-- .../Writers/TypeScript/CodeFunctionWriter.cs | 12 ++--- .../CodeInterfaceDeclarationWriter.cs | 2 +- .../Writers/TypeScript/TypeScriptWriter.cs | 2 +- .../CodeDOM/CodeNamespaceTests.cs | 12 ----- .../TypeScriptLanguageRefinerTests.cs | 52 +++++++++++++------ 10 files changed, 73 insertions(+), 54 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index c9223cc273..27e9312598 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,7 +6,12 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { + public IEnumerable Namespaces => InnerChildElements.Values.OfType(); + public IEnumerable Classes => InnerChildElements.Values.OfType(); + public IEnumerable Enums => InnerChildElements.Values.OfType(); + public IEnumerable Functions => InnerChildElements.Values.OfType(); public IEnumerable Interfaces => InnerChildElements.Values.OfType(); + public IEnumerable AddElements(params T[] elements) where T : CodeElement { if (elements == null || elements.Any(static x => x == null)) diff --git a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs index e4776d6149..55a0bb456d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs +++ b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs @@ -169,7 +169,7 @@ public NamespaceDifferentialTracker GetDifferential(CodeNamespace importNamespac ArgumentException.ThrowIfNullOrEmpty(namespacePrefix); if (this == importNamespace || Name.Equals(importNamespace.Name, StringComparison.OrdinalIgnoreCase)) // we're in the same namespace return new(); - var prefixLength = (namespacePrefix.Length > Math.Min(Name.Length, importNamespace.Name.Length) ? 0 : namespacePrefix.Length); + var prefixLength = namespacePrefix.Length; var currentNamespaceSegments = Name[prefixLength..] .Split(separator, StringSplitOptions.RemoveEmptyEntries); var importNamespaceSegments = importNamespace diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index a933b31189..b9da06d27c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -184,16 +184,17 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) { var exists = codeFunction.OriginalLocalMethod.Parameters - .Any(x => x?.Type?.Name == codeInterface.Name); + .Any(x => x?.Type?.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false); if (exists) functions.Add(codeFunction); } - else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory)) + else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory) && + codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && + codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace)) { - if (codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace)) - functions.Add(codeFunction); + functions.Add(codeFunction); } } } @@ -208,24 +209,24 @@ private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeName .SelectMany(static x => x.Parameters) .Where(static x => x.IsOfKind(CodeParameterKind.RequestConfiguration)) .Select(static x => x?.Type?.Name) - .Where(static x => x != null) - .Select(static x => x!) + .Where(static x => !string.IsNullOrEmpty(x)) + .OfType() .ToList(); List configClasses = codeNamespace.FindChildrenByName(elementNames, false) .Where(static x => x != null) - .Select(static x => x!) + .OfType() .ToList(); List queryParamClassNames = configClasses.Where(x => x != null) .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) - .Where(static s => s != null) - .Select(static s => s!) + .Where(static x => !string.IsNullOrEmpty(x)) + .OfType() .ToList(); List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) .Where(static x => x != null) - .Select(static x => x!) + .OfType() .ToList(); List elements = new List { codeClass }; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs index bba31ba362..06c318ff7e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs @@ -20,7 +20,7 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit conventions.WriteAutoGeneratedStart(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - if (codeElement.Parent?.Parent is not CodeFile) + if (codeElement.Parent?.Parent is CodeNamespace) _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); var inheritSymbol = codeElement.Inherits is null ? string.Empty : conventions.GetTypeString(codeElement.Inherits, codeElement); diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index 21b147492f..efb201dfe8 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -6,7 +6,11 @@ namespace Kiota.Builder.Writers.TypeScript; public class CodeFileDeclarationWriter : BaseElementWriter { - public CodeFileDeclarationWriter(TypeScriptConventionService conventionService) : base(conventionService) { } + private readonly CodeUsingWriter _codeUsingWriter; + public CodeFileDeclarationWriter(TypeScriptConventionService conventionService, string clientNamespaceName) : base(conventionService) + { + _codeUsingWriter = new(clientNamespaceName); + } public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageWriter writer) { @@ -15,7 +19,6 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW if (codeElement.Parent is CodeFile cf && cf.Parent is CodeNamespace ns) { - CodeUsingWriter codeUsingWriter = new(ns.Name); var usings = cf.GetChildElements().SelectMany(static x => { var startBlockUsings = x switch @@ -28,7 +31,15 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW return startBlockUsings; } ); - codeUsingWriter.WriteCodeElement(usings, ns, writer); + try + { + _codeUsingWriter.WriteCodeElement(usings, ns, writer); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 170950e5d7..157ba88a85 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -25,12 +25,12 @@ 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); } - + var codeMethod = codeElement.OriginalLocalMethod; var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty; @@ -92,12 +92,8 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private string getDeserializationFunction(CodeElement codeElement, string returnType) { - var parent = codeElement.Parent switch - { - CodeNamespace codeNamespace => codeNamespace.FindChildByName($"deserializeInto{returnType}"), - CodeFile codeFile => codeFile.FindChildByName($"deserializeInto{returnType}"), - _ => throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace or file") - }; + CodeNamespace codeNamespace = codeElement.GetImmediateParentOfType(); + CodeFunction parent = codeNamespace.FindChildByName($"deserializeInto{returnType}")!; return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false); } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs index efbbd4bec5..c713e78e52 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs @@ -17,7 +17,7 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - + var parentNamespace = codeElement.GetImmediateParentOfType(); if (codeElement.Parent?.Parent is CodeNamespace) { diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index 0e0c0a7d0f..4984b1ab56 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,6 +17,6 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName, bool usesBa AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeNameSpaceWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeInterfaceDeclarationWriter(conventionService, clientNamespaceName)); - AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeFileDeclarationWriter(conventionService, clientNamespaceName)); } } diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs index b4eaf8e1c2..763b4300dd 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs @@ -139,16 +139,4 @@ public void IsParentOf() Assert.False(grandchild.IsParentOf(child)); } - [Fact] - public void GetDifferential() - { - - var root = CodeNamespace.InitRootNamespace(); - root.Name = "Nyt.model.item"; - var child1 = root.AddNamespace("Nyt.model.item"); - var child2 = root.AddNamespace("Nyt.model"); - - - child1.GetDifferential(child2, child1.Name, '.'); - } } diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index a0ff8d116c..d2b44073a0 100644 --- a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs +++ b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs @@ -267,7 +267,8 @@ public async Task EscapesReservedKeywords() { var model = TestHelper.CreateModelClass(root, "break"); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - var interFaceModel = root.Interfaces.First(x => "BreakEscaped".Equals(x.Name, StringComparison.Ordinal)); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var interFaceModel = codeFile.Interfaces.First(x => "BreakEscaped".Equals(x.Name, StringComparison.Ordinal)); Assert.NotEqual("break", interFaceModel.Name); Assert.Contains("Escaped", interFaceModel.Name); } @@ -372,9 +373,11 @@ public async Task CorrectsCoreType() await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - var interFaceModel = root.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); - var deserializerFunction = root.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}"); - var serializationFunction = root.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}"); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + + var interFaceModel = codeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + var deserializerFunction = codeFile.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}"); + var serializationFunction = codeFile.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}"); Assert.Empty(interFaceModel.Properties.Where(x => HttpCoreDefaultName.Equals(x.Type.Name))); Assert.Empty(interFaceModel.Properties.Where(x => FactoryDefaultName.Equals(x.Type.Name))); Assert.Empty(interFaceModel.Properties.Where(x => DateTimeOffsetDefaultName.Equals(x.Type.Name))); @@ -400,7 +403,8 @@ public async Task ReplacesDateTimeOffsetByNativeType() }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - var modelInterface = root.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = codeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); Assert.NotEmpty(modelInterface.StartBlock.Usings); Assert.Equal("Date", modelInterface.Properties.First(x => x.Name == codeProperty.Name).Type.Name); } @@ -419,7 +423,8 @@ public async Task ReplacesGuidsByRespectiveType() }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - var modelInterface = root.Interfaces.First(x => x.Name.Equals(model.Name, StringComparison.OrdinalIgnoreCase)); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = codeFile.Interfaces.First(x => x.Name.Equals(model.Name, StringComparison.OrdinalIgnoreCase)); Assert.NotEmpty(modelInterface.StartBlock.Usings); Assert.NotEmpty(modelInterface.StartBlock.Usings.Where(static x => x.Name.Equals("Guid", StringComparison.Ordinal))); Assert.Equal("Guid", modelInterface.Properties.First(x => x.Name.Equals(codeProperty.Name, StringComparison.OrdinalIgnoreCase)).Type.Name, StringComparer.OrdinalIgnoreCase); @@ -439,7 +444,8 @@ public async Task ReplacesDateOnlyByNativeType() }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); Assert.NotEmpty(model.StartBlock.Usings); - var modelInterface = root.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = codeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); Assert.NotEmpty(modelInterface.StartBlock.Usings); Assert.Equal("DateOnly", modelInterface.Properties.First(x => x.Name == codeProperty.Name).Type.Name); } @@ -457,7 +463,9 @@ public async Task ReplacesTimeOnlyByNativeType() }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); Assert.NotEmpty(model.StartBlock.Usings); - var modelInterface = root.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = codeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); Assert.NotEmpty(modelInterface.StartBlock.Usings); Assert.Equal("TimeOnly", modelInterface.Properties.First(x => x.Name == codeProperty.Name).Type.Name); @@ -477,7 +485,8 @@ public async Task ReplacesDurationByNativeType() }).First(); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); Assert.NotEmpty(model.StartBlock.Usings); - var modelInterface = root.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + var codeFile = root.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = codeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); Assert.NotEmpty(modelInterface.StartBlock.Usings); Assert.Equal("Duration", modelInterface.Properties.First(x => x.Name == codeProperty.Name).Type.Name); } @@ -557,9 +566,16 @@ public async Task AliasesDuplicateUsingSymbols() }); model.AddUsing(using2); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, root); - var modelInterface = graphNS.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); - var source1Interface = modelsNS.Interfaces.First(x => x.Name == source1.Name.ToFirstCharacterUpperCase()); - var source2Interface = submodelsNS.Interfaces.First(x => x.Name == source2.Name.ToFirstCharacterUpperCase()); + + var modelCodeFile = graphNS.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + var modelInterface = modelCodeFile.Interfaces.First(x => x.Name == model.Name.ToFirstCharacterUpperCase()); + + var source1CodeFile = modelsNS.FindChildByName(source1.Name.ToFirstCharacterUpperCase()); + var source1Interface = source1CodeFile.Interfaces.First(x => x.Name == source1.Name.ToFirstCharacterUpperCase()); + + var source2CodeFile = submodelsNS.FindChildByName(source2.Name.ToFirstCharacterUpperCase()); + var source2Interface = source2CodeFile.Interfaces.First(x => x.Name == source2.Name.ToFirstCharacterUpperCase()); + var modelUsing1 = modelInterface.Usings.First(x => x.Declaration.TypeDefinition == source2Interface); var modelUsing2 = modelInterface.Usings.First(x => x.Declaration.TypeDefinition == source1Interface); Assert.Equal(modelUsing1.Declaration.Name, modelUsing2.Declaration.Name); @@ -608,7 +624,9 @@ public async Task AddsModelInterfaceForAModelClass() var model = TestHelper.CreateModelClass(testNS, "modelA"); await ILanguageRefiner.Refine(new GenerationConfiguration { Language = GenerationLanguage.TypeScript }, testNS); - Assert.Contains(testNS.Interfaces, x => x.Name == "ModelA"); + + var codeFile = testNS.FindChildByName(model.Name.ToFirstCharacterUpperCase()); + Assert.Contains(codeFile.Interfaces, x => x.Name == "ModelA"); } [Fact] @@ -749,10 +767,10 @@ public async Task GeneratesCodeFiles() Assert.NotNull(codeFile.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); // model , interface, deserializer, serializer should be a direct descendant of the namespace - Assert.NotNull(root.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); - Assert.NotNull(root.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); - Assert.NotNull(root.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); - Assert.NotNull(root.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.Null(root.FindChildByName($"DeserializeInto{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.Null(root.FindChildByName($"Serialize{model.Name.ToFirstCharacterUpperCase()}", false)); + Assert.Null(root.FindChildByName($"create{model.Name.ToFirstCharacterUpperCase()}FromDiscriminatorValue", false)); + Assert.Null(root.FindChildByName($"{model.Name.ToFirstCharacterUpperCase()}", false)); } #endregion From ae9b568778877a485a407f77e07bd8eb20aa9139 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 20 Sep 2023 01:03:21 +0300 Subject: [PATCH 10/14] Remove null coalese --- .../Writers/TypeScript/TypeScriptRelativeImportManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs index 906ad47ae8..44a66c6206 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptRelativeImportManager.cs @@ -38,9 +38,9 @@ public override (string, string, string) GetRelativeImportPathForUsing(CodeUsing importPath += codeUsing.Name; else if (!isCodeUsingAModel) { - var typeParent = codeUsing.Declaration?.TypeDefinition?.GetImmediateParentOfType(); - var nameSpaceElement = typeParent?.FindChildByName(codeUsing.Declaration?.Name ?? codeUsing.Name); - if (nameSpaceElement?.Parent is CodeFile f) + var nameSpaceName = string.IsNullOrEmpty(codeUsing.Declaration?.Name) ? codeUsing.Name : codeUsing.Declaration.Name; + if (codeUsing.Declaration?.TypeDefinition?.GetImmediateParentOfType()? + .FindChildByName(nameSpaceName)?.Parent is CodeFile f) { importPath += f.Name.ToFirstCharacterLowerCase(); } From f670a69b0fd9007d9ea187032a9946336cfe85c2 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:17:36 +0300 Subject: [PATCH 11/14] hashet comparison --- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 13 +++++++------ .../Writers/TypeScript/CodeFileDeclarationWriter.cs | 10 +--------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index b9da06d27c..9ca32ae83c 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -244,7 +244,7 @@ private static void CorrectCodeFileUsing(CodeElement currentElement) // correct the using values // eliminate the using refering the elements in the same file - HashSet elementSet = codeFile.GetChildElements(true).Select(x => x.Name.ToLowerInvariant()).ToHashSet(); + HashSet elementSet = codeFile.GetChildElements(true).Select(x => x.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var element in codeFile.GetChildElements(true)) { var startBlockUsings = element switch @@ -259,23 +259,24 @@ private static void CorrectCodeFileUsing(CodeElement currentElement) var foundUsings = startBlockUsings .Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null) .Where(y => y.Declaration!.TypeDefinition!.GetImmediateParentOfType() == codeNamespace) - .Where(y => elementSet.Contains(y.Declaration!.TypeDefinition!.Name.ToLowerInvariant())); + .Where(y => elementSet.Contains(y.Declaration!.TypeDefinition!.Name)); foreach (var x in foundUsings) { + var declarationName = x.Declaration!.Name; switch (element) { case CodeFunction ci: - ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + ci.RemoveUsingsByDeclarationName(declarationName); break; case CodeInterface ci: - ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + ci.RemoveUsingsByDeclarationName(declarationName); break; case CodeEnum ci: - ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + ci.RemoveUsingsByDeclarationName(declarationName); break; case CodeClass ci: - ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!); + ci.RemoveUsingsByDeclarationName(declarationName); break; } } diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index efb201dfe8..a065b4f35a 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -31,15 +31,7 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW return startBlockUsings; } ); - try - { - _codeUsingWriter.WriteCodeElement(usings, ns, writer); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + _codeUsingWriter.WriteCodeElement(usings, ns, writer); } } From f6322802a3c3bcc351d08fba8275cc1a2ea6cd1f Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:58:13 +0300 Subject: [PATCH 12/14] Refactor --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 4 ---- src/Kiota.Builder/Refiners/TypeScriptRefiner.cs | 15 ++++++--------- .../TypeScript/CodeFileDeclarationWriter.cs | 3 +-- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 27e9312598..9b52d59d7a 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,10 +6,6 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { - public IEnumerable Namespaces => InnerChildElements.Values.OfType(); - public IEnumerable Classes => InnerChildElements.Values.OfType(); - public IEnumerable Enums => InnerChildElements.Values.OfType(); - public IEnumerable Functions => InnerChildElements.Values.OfType(); public IEnumerable Interfaces => InnerChildElements.Values.OfType(); 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 9ca32ae83c..32b6d1f586 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -181,14 +181,11 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames { if (element is CodeFunction codeFunction) { - if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer)) + if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer) && + codeFunction.OriginalLocalMethod.Parameters + .Any(x => x?.Type?.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false)) { - var exists = codeFunction.OriginalLocalMethod.Parameters - .Any(x => x?.Type?.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false); - - if (exists) - functions.Add(codeFunction); - + functions.Add(codeFunction); } else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory) && codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && @@ -198,8 +195,8 @@ private static void GenerateModelCodeFile(CodeInterface codeInterface, CodeNames } } } - String modelName = codeInterface.Name; - codeNamespace.TryAddCodeFile(modelName, functions.ToArray()); + + codeNamespace.TryAddCodeFile(codeInterface.Name, functions.ToArray()); } private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs index a065b4f35a..5fd774dad6 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -21,14 +21,13 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW { var usings = cf.GetChildElements().SelectMany(static x => { - var startBlockUsings = x switch + return x switch { CodeFunction f => f.StartBlock.Usings, CodeInterface ci => ci.Usings, CodeClass cc => cc.Usings, _ => Enumerable.Empty() }; - return startBlockUsings; } ); _codeUsingWriter.WriteCodeElement(usings, ns, writer); From eb3062df3b0d25fab77a3104efa2194026cdd510 Mon Sep 17 00:00:00 2001 From: rkodev <43806892+rkodev@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:12:20 +0300 Subject: [PATCH 13/14] Order items --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 9b52d59d7a..5e8e7757a9 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,7 +6,7 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { - public IEnumerable Interfaces => InnerChildElements.Values.OfType(); + public IEnumerable Interfaces => InnerChildElements.Values.OfType().OrderBy(static x => x.Name); public IEnumerable AddElements(params T[] elements) where T : CodeElement { From b480e36417b7c6300028b03961ef399d637c3e0c Mon Sep 17 00:00:00 2001 From: Ronald K <43806892+rkodev@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:18:11 +0300 Subject: [PATCH 14/14] Update src/Kiota.Builder/CodeDOM/CodeFile.cs Co-authored-by: Vincent Biret --- src/Kiota.Builder/CodeDOM/CodeFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/CodeDOM/CodeFile.cs b/src/Kiota.Builder/CodeDOM/CodeFile.cs index 5e8e7757a9..0efd93fb0a 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,7 +6,7 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { - public IEnumerable Interfaces => InnerChildElements.Values.OfType().OrderBy(static x => x.Name); + public IEnumerable Interfaces => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); public IEnumerable AddElements(params T[] elements) where T : CodeElement {