Skip to content

Commit

Permalink
Adds Code Files in TypeScript (#3321)
Browse files Browse the repository at this point in the history
* Adds codefiles in typescript

* Update src/Kiota.Builder/CodeDOM/CodeFile.cs

Co-authored-by: Vincent Biret <[email protected]>

---------

Co-authored-by: Vincent Biret <[email protected]>
  • Loading branch information
rkodev and baywet authored Sep 21, 2023
1 parent ba2f65d commit 2cc6dc6
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 27 deletions.
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
2 changes: 1 addition & 1 deletion src/Kiota.Builder/CodeRenderers/TypeScriptCodeRenderer.cs
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
37 changes: 37 additions & 0 deletions src/Kiota.Builder/Writers/TypeScript/CodeFileDeclarationWriter.cs
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

0 comments on commit 2cc6dc6

Please sign in to comment.