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
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
7 changes: 4 additions & 3 deletions src/Kiota.Builder/CodeDOM/CodeFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Kiota.Builder.CodeDOM;

public class CodeFile : CodeBlock<CodeFileDeclaration, CodeFileBlockEnd>
{
public IEnumerable<CodeInterface> Interfaces => InnerChildElements.Values.OfType<CodeInterface>();
public IEnumerable<T> AddElements<T>(params T[] elements) where T : CodeElement
{
if (elements == null || elements.Any(static x => x == null))
Expand All @@ -17,9 +18,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
3 changes: 2 additions & 1 deletion 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 Expand Up @@ -168,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;
var prefixLength = (namespacePrefix.Length > Math.Min(Name.Length, importNamespace.Name.Length) ? 0 : namespacePrefix.Length);
rkodev marked this conversation as resolved.
Show resolved Hide resolved
var currentNamespaceSegments = Name[prefixLength..]
.Split(separator, StringSplitOptions.RemoveEmptyEntries);
var importNamespaceSegments = importNamespace
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());
}
}
133 changes: 133 additions & 0 deletions src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,142 @@ 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))
{
var exists = codeFunction.OriginalLocalMethod.Parameters
.Any(x => x?.Type?.Name == codeInterface.Name);
rkodev marked this conversation as resolved.
Show resolved Hide resolved

if (exists)
functions.Add(codeFunction);

}
else if (codeFunction.OriginalLocalMethod.IsOfKind(CodeMethodKind.Factory))
{
if (codeInterface.Name.EqualsIgnoreCase(codeFunction.OriginalMethodParentClass.Name) && codeFunction.OriginalMethodParentClass.IsChildOf(codeNamespace))
rkodev marked this conversation as resolved.
Show resolved Hide resolved
functions.Add(codeFunction);
}
}
}
String modelName = codeInterface.Name;
codeNamespace.TryAddCodeFile(modelName, functions.ToArray());
baywet marked this conversation as resolved.
Show resolved Hide resolved
}

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 => x != null)
.Select(static x => x!)
rkodev marked this conversation as resolved.
Show resolved Hide resolved
.ToList();

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

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

List<CodeInterface> queryParamClasses = codeNamespace.FindChildrenByName<CodeInterface>(queryParamClassNames, false)
.Where(static x => x != null)
.Select(static x => x!)
.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.ToLowerInvariant()).ToHashSet();
rkodev marked this conversation as resolved.
Show resolved Hide resolved
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.ToLowerInvariant()));

foreach (var x in foundUsings)
{
switch (element)
{
case CodeFunction ci:
ci.RemoveUsingsByDeclarationName(x.Declaration?.Name!);
break;
andrueastman marked this conversation as resolved.
Show resolved Hide resolved
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<CodeUsing> usings, string currentSymbolName)
{
var enumeratedUsings = usings.ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit
ArgumentNullException.ThrowIfNull(codeElement);
ArgumentNullException.ThrowIfNull(writer);
var parentNamespace = codeElement.GetImmediateParentOfType<CodeNamespace>();
_codeUsingWriter.WriteCodeElement(codeElement.Usings, parentNamespace, writer);

if (codeElement.Parent?.Parent is not CodeFile)
rkodev marked this conversation as resolved.
Show resolved Hide resolved
_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,35 @@
using System;
using System.Linq;
using Kiota.Builder.CodeDOM;

namespace Kiota.Builder.Writers.TypeScript;

public class CodeFileDeclarationWriter : BaseElementWriter<CodeFileDeclaration, TypeScriptConventionService>
{
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<CodeUsing>()
};
return startBlockUsings;
}
baywet marked this conversation as resolved.
Show resolved Hide resolved
);
codeUsingWriter.WriteCodeElement(usings, ns, writer);
}
}

}
16 changes: 10 additions & 6 deletions src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeNamespace>(), 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<CodeNamespace>(), writer);

var codeMethod = codeElement.OriginalLocalMethod;

var returnType = codeMethod.Kind != CodeMethodKind.Factory ? conventions.GetTypeString(codeMethod.ReturnType, codeElement) : string.Empty;
Expand Down Expand Up @@ -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<CodeFunction>($"deserializeInto{returnType}");
CodeNamespace codeNamespace => codeNamespace.FindChildByName<CodeFunction>($"deserializeInto{returnType}"),
CodeFile codeFile => codeFile.FindChildByName<CodeFunction>($"deserializeInto{returnType}"),
_ => throw new InvalidOperationException($"{codeElement.Name} does not have a parent namespace or file")
};

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

var parentNamespace = codeElement.GetImmediateParentOfType<CodeNamespace>();
_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} {{");
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 typeParent = codeUsing.Declaration?.TypeDefinition?.GetImmediateParentOfType<CodeNamespace>();
var nameSpaceElement = typeParent?.FindChildByName<CodeElement>(codeUsing.Declaration?.Name ?? codeUsing.Name);
if (nameSpaceElement?.Parent is CodeFile f)
baywet marked this conversation as resolved.
Show resolved Hide resolved
{
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));
}
}
13 changes: 13 additions & 0 deletions tests/Kiota.Builder.Tests/CodeDOM/CodeNamespaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, '.');
}
}
Loading
Loading