Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Code Files in TypeScript #3321

Merged
merged 18 commits into from
Sep 21, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions src/Kiota.Builder/CodeDOM/CodeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ public IEnumerable<T> FindChildrenByName<T>(string childName) where T : ICodeEle

return Enumerable.Empty<T>();
}

public IEnumerable<T?> FindChildrenByName<T>(IEnumerable<string> 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<T>(x, findInChildElements));
}

public T? FindChildByName<T>(string childName, bool findInChildElements = true) where T : ICodeElement
{
ArgumentException.ThrowIfNullOrEmpty(childName);
Expand Down
8 changes: 5 additions & 3 deletions src/Kiota.Builder/CodeDOM/CodeFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ namespace Kiota.Builder.CodeDOM;

public class CodeFile : CodeBlock<CodeFileDeclaration, CodeFileBlockEnd>
{
public IEnumerable<CodeInterface> Interfaces => InnerChildElements.Values.OfType<CodeInterface>().OrderBy(static x => x.Name, StringComparer.Ordinal);

public IEnumerable<T> AddElements<T>(params T[] elements) where T : CodeElement
{
if (elements == null || elements.Any(static x => x == null))
Expand All @@ -17,9 +19,9 @@ public IEnumerable<T> AddElements<T>(params T[] elements) where T : CodeElement
}

public IEnumerable<CodeUsing> AllUsingsFromChildElements => GetChildElements()
.SelectMany(static x => x.GetChildElements())
.OfType<ProprietableBlockDeclaration>()
.SelectMany(static x => x.Usings);
.SelectMany(static x => x.GetChildElements())
.OfType<ProprietableBlockDeclaration>()
.SelectMany(static x => x.Usings);
}
public class CodeFileDeclaration : ProprietableBlockDeclaration
{
Expand Down
1 change: 1 addition & 0 deletions src/Kiota.Builder/CodeDOM/CodeNamespace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public CodeNamespace GetRootNamespace()
public IEnumerable<CodeEnum> Enums => InnerChildElements.Values.OfType<CodeEnum>();
public IEnumerable<CodeFunction> Functions => InnerChildElements.Values.OfType<CodeFunction>();
public IEnumerable<CodeInterface> Interfaces => InnerChildElements.Values.OfType<CodeInterface>();
public IEnumerable<CodeFile> Files => InnerChildElements.Values.OfType<CodeFile>();
public CodeNamespace? FindNamespaceByName(string nsName)
{
ArgumentException.ThrowIfNullOrEmpty(nsName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
132 changes: 132 additions & 0 deletions src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeElement> functions = new List<CodeElement> { 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<String> 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<string>()
.ToList();

List<CodeInterface> configClasses = codeNamespace.FindChildrenByName<CodeInterface>(elementNames, false)
.Where(static x => x != null)
.OfType<CodeInterface>()
.ToList();

List<string> queryParamClassNames = configClasses.Where(x => x != null)
.Select(static w => w.GetPropertyOfKind(CodePropertyKind.QueryParameters)?.Type?.Name)
.Where(static x => !string.IsNullOrEmpty(x))
.OfType<string>()
.ToList();

List<CodeInterface> queryParamClasses = codeNamespace.FindChildrenByName<CodeInterface>(queryParamClassNames, false)
.Where(static x => x != null)
.OfType<CodeInterface>()
.ToList();

List<CodeElement> elements = new List<CodeElement> { 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<string> 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<CodeUsing>()
};

var foundUsings = startBlockUsings
.Where(static x => x.Declaration != null && x.Declaration.TypeDefinition != null)
.Where(y => y.Declaration!.TypeDefinition!.GetImmediateParentOfType<CodeNamespace>() == 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<CodeUsing> usings, string currentSymbolName)
{
var enumeratedUsings = usings.ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit
if (codeElement.Parent?.Parent is CodeNamespace)
conventions.WriteAutoGeneratedStart(writer);
var parentNamespace = codeElement.GetImmediateParentOfType<CodeNamespace>();
_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}") +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Linq;
using Kiota.Builder.CodeDOM;

namespace Kiota.Builder.Writers.TypeScript;

public class CodeFileDeclarationWriter : BaseElementWriter<CodeFileDeclaration, TypeScriptConventionService>
{
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<CodeUsing>()
};
}
);
_codeUsingWriter.WriteCodeElement(usings, ns, writer);
}
}

}
17 changes: 10 additions & 7 deletions src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeNamespace>(), writer);
if (codeElement.Parent is CodeNamespace)
{
_codeUsingWriter.WriteCodeElement(codeElement.StartBlock.Usings, codeElement.GetImmediateParentOfType<CodeNamespace>(), writer);
}

var codeMethod = codeElement.OriginalLocalMethod;

var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty;
Expand Down Expand Up @@ -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<CodeFunction>($"deserializeInto{returnType}");
CodeNamespace codeNamespace = codeElement.GetImmediateParentOfType<CodeNamespace>();
CodeFunction parent = codeNamespace.FindChildByName<CodeFunction>($"deserializeInto{returnType}")!;

return conventions.GetTypeString(new CodeType { TypeDefinition = parent }, codeElement, false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ public override void WriteCodeElement(InterfaceDeclaration codeElement, Language

var parentNamespace = codeElement.GetImmediateParentOfType<CodeNamespace>();
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} {{");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter
writer.WriteLines(codeElement.Enums
.Concat<CodeElement>(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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeNamespace>()?
.FindChildByName<CodeElement>(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);
}
Expand Down
1 change: 1 addition & 0 deletions src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
1 change: 1 addition & 0 deletions tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,5 @@ public void IsParentOf()
Assert.True(child.IsParentOf(grandchild));
Assert.False(grandchild.IsParentOf(child));
}

}
Loading
Loading