diff --git a/CHANGELOG.md b/CHANGELOG.md index 703427eec2..5de990cbd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed parameter order in with_url method body to match the signature of RequestBuilder constructor in Python. [#3328](https://github.com/microsoft/kiota/issues/3328 - Removed redundant undefined qualifier in TypeScript for properties. [#3244](https://github.com/microsoft/kiota/issues/3244) - The default status code response is now used as 4XX and 5XX when those class responses are not provided in the description. [#3245](https://github.com/microsoft/kiota/issues/3245) +- 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 diff --git a/src/Kiota.Builder/CodeDOM/CodeBlock.cs b/src/Kiota.Builder/CodeDOM/CodeBlock.cs index be31a2b068..8931e9d2fd 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(static 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..0efd93fb0a 100644 --- a/src/Kiota.Builder/CodeDOM/CodeFile.cs +++ b/src/Kiota.Builder/CodeDOM/CodeFile.cs @@ -6,6 +6,8 @@ namespace Kiota.Builder.CodeDOM; public class CodeFile : CodeBlock { + public IEnumerable Interfaces => InnerChildElements.Values.OfType().OrderBy(static x => x.Name, StringComparer.Ordinal); + public IEnumerable AddElements(params T[] elements) where T : CodeElement { if (elements == null || elements.Any(static x => x == null)) @@ -17,9 +19,9 @@ 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 class CodeFileDeclaration : ProprietableBlockDeclaration { diff --git a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs index de63839750..55a0bb456d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeNamespace.cs +++ b/src/Kiota.Builder/CodeDOM/CodeNamespace.cs @@ -70,6 +70,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); diff --git a/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs index 21a601e828..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(); + 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 19bda1990a..32b6d1f586 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -148,9 +148,141 @@ 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) && + codeFunction.OriginalLocalMethod.Parameters + .Any(x => x?.Type?.Name?.Equals(codeInterface.Name, StringComparison.OrdinalIgnoreCase) ?? false)) + { + functions.Add(codeFunction); + } + else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory) && + codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && + codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace)) + { + functions.Add(codeFunction); + } + } + } + + codeNamespace.TryAddCodeFile(codeInterface.Name, functions.ToArray()); + } + + private static void GenerateRequestBuilderCodeFile(CodeClass codeClass, CodeNamespace codeNamespace) + { + List elementNames = codeClass.Methods + .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 => !string.IsNullOrEmpty(x)) + .OfType() + .ToList(); + + List configClasses = codeNamespace.FindChildrenByName(elementNames, false) + .Where(static x => x != null) + .OfType() + .ToList(); + + List queryParamClassNames = configClasses.Where(x => x != null) + .Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name) + .Where(static x => !string.IsNullOrEmpty(x)) + .OfType() + .ToList(); + + List queryParamClasses = codeNamespace.FindChildrenByName(queryParamClassNames, false) + .Where(static x => x != null) + .OfType() + .ToList(); + + List elements = new List { codeClass }; + elements.AddRange(queryParamClasses); + elements.AddRange(configClasses); + + codeNamespace.TryAddCodeFile(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).ToHashSet(StringComparer.OrdinalIgnoreCase); + 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)); + + foreach (var x in foundUsings) + { + var declarationName = x.Declaration!.Name; + switch (element) + { + case CodeFunction ci: + ci.RemoveUsingsByDeclarationName(declarationName); + break; + case CodeInterface ci: + ci.RemoveUsingsByDeclarationName(declarationName); + break; + case CodeEnum ci: + ci.RemoveUsingsByDeclarationName(declarationName); + break; + case CodeClass ci: + ci.RemoveUsingsByDeclarationName(declarationName); + 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 a5614cf670..06c318ff7e 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeClassDeclarationWriter.cs @@ -19,7 +19,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit if (codeElement.Parent?.Parent is CodeNamespace) conventions.WriteAutoGeneratedStart(writer); var parentNamespace = codeElement.GetImmediateParentOfType(); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + + 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); var derivation = (string.IsNullOrEmpty(inheritSymbol) ? string.Empty : $" extends {inheritSymbol}") + diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs new file mode 100644 index 0000000000..5fd774dad6 --- /dev/null +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.TypeScript; + +public class CodeFileDeclarationWriter : BaseElementWriter +{ + private readonly CodeUsingWriter _codeUsingWriter; + public CodeFileDeclarationWriter(TypeScriptConventionService conventionService, string clientNamespaceName) : base(conventionService) + { + _codeUsingWriter = new(clientNamespaceName); + } + + 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) + { + var usings = cf.GetChildElements().SelectMany(static x => + { + return x switch + { + CodeFunction f => f.StartBlock.Usings, + CodeInterface ci => ci.Usings, + CodeClass cc => cc.Usings, + _ => Enumerable.Empty() + }; + } + ); + _codeUsingWriter.WriteCodeElement(usings, ns, writer); + } + } + +} diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs index 851e4bd1fe..157ba88a85 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs @@ -22,9 +22,15 @@ 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"); + + 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); - _codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType(), writer); + 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; @@ -86,11 +92,8 @@ private void WriteFactoryMethodBody(CodeFunction codeElement, string returnType, private string getDeserializationFunction(CodeElement codeElement, string returnType) { - if (codeElement.Parent is not CodeNamespace codeNamespace) - { - throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace"); - } - var parent = codeNamespace.FindChildByName($"deserializeInto{returnType}"); + 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 71b86539fc..c713e78e52 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeInterfaceDeclarationWriter.cs @@ -20,8 +20,10 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language var parentNamespace = codeElement.GetImmediateParentOfType(); if (codeElement.Parent?.Parent is CodeNamespace) + { conventions.WriteAutoGeneratedStart(writer); - _codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer); + _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..44a66c6206 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 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(); + } + 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 1ff6e07ca2..11233a0f31 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -17,5 +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, clientNamespaceName)); } } diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs index 7130b293d5..763b4300dd 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs +++ b/tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs @@ -138,4 +138,5 @@ public void IsParentOf() Assert.True(child.IsParentOf(grandchild)); Assert.False(grandchild.IsParentOf(child)); } + } diff --git a/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/TypeScriptLanguageRefinerTests.cs index 53c2c005c1..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] @@ -637,5 +655,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.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 }