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

Typescript - introducing index files namespace wise fixing circular dependency #1274

Merged
merged 34 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b244b60
record requestoption
nikithauc Feb 11, 2022
bc3d467
adding typescript namespace gen
nikithauc Feb 22, 2022
5c559d8
Merge branch 'main' into nikithauc/test-index
nikithauc Feb 22, 2022
464a8f0
adding gets enums to namespace
nikithauc Feb 22, 2022
8b5968b
Merge branch 'main' into nikithauc/test-index
nikithauc Feb 24, 2022
7ae65d4
recursive inheritance ordering
nikithauc Feb 25, 2022
bbbafed
checking namespace of parent
nikithauc Feb 25, 2022
c893bfa
adding rendering condition
nikithauc Feb 26, 2022
cd640c7
adding comments
nikithauc Feb 28, 2022
b1ce0a2
- removes testing file
baywet Feb 28, 2022
cf0aa36
adding language specific code renderer
nikithauc Mar 2, 2022
b220ecc
Merge branch 'nikithauc/test-index' of https://github.com/microsoft/k…
nikithauc Mar 2, 2022
cc6ac73
Merge branch 'main' into nikithauc/test-index
baywet Mar 2, 2022
c42dbe2
relativeimport language specific, model only barrel
nikithauc Mar 4, 2022
29bdb85
Merge branch 'main' into nikithauc/test-index
nikithauc Mar 16, 2022
f5cceba
extending relativeimportmanager; relative imports only for class
nikithauc Mar 16, 2022
bf7dc95
adding unit test for codenamespace writer
nikithauc Mar 17, 2022
ea58a17
resetting files
nikithauc Mar 17, 2022
c8672fe
resetting launcsettings
nikithauc Mar 17, 2022
d8dc928
ussing addinnerclass for test
nikithauc Mar 17, 2022
285506d
Apply suggestions from code review
nikithauc Mar 17, 2022
c2e6306
reset relativeimport code, update working test
nikithauc Mar 17, 2022
40e82fe
Merge branch 'nikithauc/test-index' of https://github.com/microsoft/k…
nikithauc Mar 17, 2022
305b819
missing using
nikithauc Mar 17, 2022
e354f9d
Update tests/Kiota.Builder.Tests/Writers/TypeScript/CodeNamespaceWrit…
nikithauc Mar 18, 2022
43bf414
Apply suggestions from code review
baywet Mar 18, 2022
859fb7d
Merge branch 'main' into nikithauc/test-index
nikithauc Mar 18, 2022
afa0305
- fixes a bug wwhere the root namespace would have a null name causin…
baywet Mar 18, 2022
69156a1
- code linting
baywet Mar 18, 2022
5650584
Merge branch 'main' into nikithauc/test-index
nikithauc Mar 18, 2022
1e0af62
renaming relativeimportmanager test
nikithauc Mar 21, 2022
4e0892f
Update CHANGELOG.md
nikithauc Mar 21, 2022
f3804be
- moves the changelog entry to the right place
baywet Mar 21, 2022
7998bec
Merge branch 'main' into nikithauc/test-index
baywet Mar 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
1 change: 1 addition & 0 deletions src/Kiota.Builder/CodeDOM/CodeNamespace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public CodeNamespace GetRootNamespace() {
}
public IEnumerable<CodeNamespace> Namespaces => InnerChildElements.Values.OfType<CodeNamespace>();
public IEnumerable<CodeClass> Classes => InnerChildElements.Values.OfType<CodeClass>();
public IEnumerable<CodeEnum> Enums => InnerChildElements.Values.OfType<CodeEnum>();
public CodeNamespace FindNamespaceByName(string nsName) {
if(string.IsNullOrEmpty(nsName)) throw new ArgumentNullException(nameof(nsName));
if(nsName.Equals(Name)) return this;
Expand Down
9 changes: 7 additions & 2 deletions src/Kiota.Builder/CodeRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ public async Task RenderCodeNamespaceToFilePerClassAsync(LanguageWriter writer,
{
var namespaceNameLastSegment = codeNamespace.Name.Split('.').Last().ToLowerInvariant();
// if the module already has a class with the same name, it's going to be declared automatically
if (_configuration.ShouldWriteBarrelsIfClassExists ||
codeNamespace.FindChildByName<CodeClass>(namespaceNameLastSegment, false) == null)
if (_configuration.ShouldWriteBarrelsIfClassExists && _configuration.setCodeRenderingCondition(codeNamespace))
// TODO : Verify and plug the following condition in the language specific index rendering condition
//codeNamespace.FindChildByName<CodeClass>(namespaceNameLastSegment, false) == null)
baywet marked this conversation as resolved.
Show resolved Hide resolved

{
await RenderCodeNamespaceToSingleFileAsync(writer, codeNamespace, writer.PathSegmenter.GetPath(root, codeNamespace));
}

}
await RenderCodeNamespaceToFilePerClassAsync(writer, codeNamespace);
}
Expand Down
9 changes: 8 additions & 1 deletion src/Kiota.Builder/GenerationConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

namespace Kiota.Builder {
public class GenerationConfiguration {
Expand All @@ -22,9 +23,15 @@ public class GenerationConfiguration {
private static readonly HashSet<GenerationLanguage> BarreledLanguages = new () {
GenerationLanguage.Ruby,
// TODO: add typescript once we have a barrel writer for it
baywet marked this conversation as resolved.
Show resolved Hide resolved
GenerationLanguage.TypeScript
};
private static readonly HashSet<GenerationLanguage> BarreledLanguagesWithConstantFileName = new () {
//TODO: add typescript once we have a barrel writer for it
baywet marked this conversation as resolved.
Show resolved Hide resolved

GenerationLanguage.TypeScript
};

public Func<CodeNamespace, bool> setCodeRenderingCondition;

}
}
443 changes: 274 additions & 169 deletions src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/Kiota.Builder/Refiners/RubyRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ public override void Refine(CodeNamespace generatedCode)
new [] { "microsoft_kiota_abstractions.ApiClientBuilder",
"microsoft_kiota_abstractions.SerializationWriterFactoryRegistry" },
new [] { "microsoft_kiota_abstractions.ParseNodeFactoryRegistry" });
setNameSpaceRenderingCondition(_configuration);
}

private static void setNameSpaceRenderingCondition(GenerationConfiguration configuration)
{
configuration.setCodeRenderingCondition = RenderNamespace;
}
// TODO : Update this condition
public static bool RenderNamespace(CodeNamespace codeNamespace)
{
return (codeNamespace.Classes.Any() || codeNamespace.Enums.Any());
}
private static void CorrectPropertyType(CodeProperty currentProperty) {
if(currentProperty.IsOfKind(CodePropertyKind.PathParameters)) {
Expand Down
216 changes: 140 additions & 76 deletions src/Kiota.Builder/Refiners/TypeScriptRefiner.cs

Large diffs are not rendered by default.

65 changes: 40 additions & 25 deletions src/Kiota.Builder/Writers/RelativeImportManager.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Kiota.Builder.Extensions;

namespace Kiota.Builder.Writers {
public class RelativeImportManager {
namespace Kiota.Builder.Writers
{
public class RelativeImportManager
{
private readonly int prefixLength;
private readonly char separator;
public RelativeImportManager(string namespacePrefix, char namespaceSeparator)
{
if(string.IsNullOrEmpty(namespacePrefix))
if (string.IsNullOrEmpty(namespacePrefix))
throw new ArgumentNullException(nameof(namespacePrefix));
if(namespaceSeparator == default)
if (namespaceSeparator == default)
throw new ArgumentNullException(nameof(namespaceSeparator));

prefixLength = namespacePrefix.Length;
Expand All @@ -23,36 +25,44 @@ public RelativeImportManager(string namespacePrefix, char namespaceSeparator)
/// <param name="codeUsing">The using to import into the current namespace context</param>
/// <param name="currentNamespace">The current namespace</param>
/// <returns>The import symbol, it's alias if any and the relative import path</returns>
public (string, string, string) GetRelativeImportPathForUsing(CodeUsing codeUsing, CodeNamespace currentNamespace) {
if(codeUsing?.IsExternal ?? true)
public (string, string, string) GetRelativeImportPathForUsing(CodeUsing codeUsing, CodeNamespace currentNamespace)
{
if (codeUsing?.IsExternal ?? true)
return (string.Empty, string.Empty, string.Empty);//it's an external import, add nothing
var typeDef = codeUsing.Declaration.TypeDefinition;

var importSymbol = codeUsing.Declaration?.Name?.ToFirstCharacterUpperCase() ?? codeUsing.Name;

if(typeDef == null)
if (typeDef == null)
return (importSymbol, codeUsing.Alias, "./"); // it's relative to the folder, with no declaration (default failsafe)
else {
var importPath = GetImportRelativePathFromNamespaces(currentNamespace,
else
{
var importPath = GetImportRelativePathFromNamespaces(currentNamespace,
typeDef.GetImmediateParentOfType<CodeNamespace>());
if(string.IsNullOrEmpty(importPath))
importPath+= codeUsing.Name;
else
importPath+= codeUsing.Declaration.Name.ToFirstCharacterLowerCase();
if (importPath == "./")
baywet marked this conversation as resolved.
Show resolved Hide resolved
{
importPath += "index";
}
else if (string.IsNullOrEmpty(importPath))
{
importPath += codeUsing.Name;
}
return (importSymbol, codeUsing.Alias, importPath);
}
}
private string GetImportRelativePathFromNamespaces(CodeNamespace currentNamespace, CodeNamespace importNamespace) {
if(currentNamespace == null)
private string GetImportRelativePathFromNamespaces(CodeNamespace currentNamespace, CodeNamespace importNamespace)
{
if (currentNamespace == null)
throw new ArgumentNullException(nameof(currentNamespace));
else if (importNamespace == null)
throw new ArgumentNullException(nameof(importNamespace));
else if(currentNamespace == importNamespace || currentNamespace.Name.Equals(importNamespace.Name, StringComparison.OrdinalIgnoreCase)) // we're in the same namespace
else if (currentNamespace == importNamespace || currentNamespace.Name.Equals(importNamespace.Name, StringComparison.OrdinalIgnoreCase)) // we're in the same namespace
return "./";
else
return GetRelativeImportPathFromSegments(currentNamespace, importNamespace);
return GetRelativeImportPathFromSegments(currentNamespace, importNamespace);
}
private string GetRelativeImportPathFromSegments(CodeNamespace currentNamespace, CodeNamespace importNamespace) {
private string GetRelativeImportPathFromSegments(CodeNamespace currentNamespace, CodeNamespace importNamespace)
{
var currentNamespaceSegments = currentNamespace
.Name[prefixLength..]
.Split(separator, StringSplitOptions.RemoveEmptyEntries);
Expand All @@ -62,24 +72,29 @@ private string GetRelativeImportPathFromSegments(CodeNamespace currentNamespace,
var importNamespaceSegmentsCount = importNamespaceSegments.Length;
var currentNamespaceSegmentsCount = currentNamespaceSegments.Length;
var deeperMostSegmentIndex = 0;
while(deeperMostSegmentIndex < Math.Min(importNamespaceSegmentsCount, currentNamespaceSegmentsCount)) {
if(currentNamespaceSegments.ElementAt(deeperMostSegmentIndex).Equals(importNamespaceSegments.ElementAt(deeperMostSegmentIndex), StringComparison.OrdinalIgnoreCase))
while (deeperMostSegmentIndex < Math.Min(importNamespaceSegmentsCount, currentNamespaceSegmentsCount))
{
if (currentNamespaceSegments.ElementAt(deeperMostSegmentIndex).Equals(importNamespaceSegments.ElementAt(deeperMostSegmentIndex), StringComparison.OrdinalIgnoreCase))
deeperMostSegmentIndex++;
else
break;
}
if (deeperMostSegmentIndex == currentNamespaceSegmentsCount) { // we're in a parent namespace and need to import with a relative path
if (deeperMostSegmentIndex == currentNamespaceSegmentsCount)
{ // we're in a parent namespace and need to import with a relative path
return "./" + GetRemainingImportPath(importNamespaceSegments.Skip(deeperMostSegmentIndex));
} else { // we're in a sub namespace and need to go "up" with dot dots
}
else
{ // we're in a sub namespace and need to go "up" with dot dots
var upMoves = currentNamespaceSegmentsCount - deeperMostSegmentIndex;
var pathSegmentSeparator = upMoves > 0 ? "/" : string.Empty;
return string.Join("/", Enumerable.Repeat("..", upMoves)) +
pathSegmentSeparator +
GetRemainingImportPath(importNamespaceSegments.Skip(deeperMostSegmentIndex));
}
}
private static string GetRemainingImportPath(IEnumerable<string> remainingSegments) {
if(remainingSegments.Any())
private static string GetRemainingImportPath(IEnumerable<string> remainingSegments)
{
if (remainingSegments.Any())
return remainingSegments.Select(x => x.ToFirstCharacterLowerCase()).Aggregate((x, y) => $"{x}/{y}") + '/';
else
return string.Empty;
Expand Down
115 changes: 115 additions & 0 deletions src/Kiota.Builder/Writers/TypeScript/CodeNameSpaceWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Linq;

namespace Kiota.Builder.Writers.TypeScript
baywet marked this conversation as resolved.
Show resolved Hide resolved
{
public class CodeNameSpaceWriter : BaseElementWriter<CodeNamespace, TypeScriptConventionService>
{
public CodeNameSpaceWriter(TypeScriptConventionService conventionService) : base(conventionService) { }

/// <summary>
/// Writes export statements for classes and enums belonging to a namespace into a generated index.ts file.
/// The classes should be export in the order of inheritance so as to avoid circular dependency issues in javascript.
/// </summary>
/// <param name="codeElement">Code element is a code namespace</param>
/// <param name="writer"></param>
public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer)
{
var sortedClassNames = SortClassesInOrderOfInheritance(codeElement.Classes.ToList());
baywet marked this conversation as resolved.
Show resolved Hide resolved

foreach (var className in sortedClassNames)
{
writer.WriteLine($"export * from './{className}'");
}

var enums = codeElement.Enums;
if (enums != null && enums.Any())
{
foreach (var e in enums)
{
writer.WriteLine($"export * from './{e.Name}'");
baywet marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// <summary>
/// Visits every child for a given parent class and recursively inserts each class into a list ordered based on inheritance.
/// </summary>
/// <param name="parentListChildren"></param>
/// <param name="visited"></param>
/// <param name="orderedList"> Lis</param>
/// <param name="current"></param>
private void VisitEveryChild(Dictionary<string, List<string>> parentChildrenMap, HashSet<string> visited, List<string> inheritanceOrderList, string current)
{
if (!visited.Contains(current))
{
visited.Add(current);

foreach (var child in parentChildrenMap[current])
{
if (parentChildrenMap.ContainsKey(child))
{
VisitEveryChild(parentChildrenMap, visited, inheritanceOrderList, child);

baywet marked this conversation as resolved.
Show resolved Hide resolved
}
}
inheritanceOrderList.Insert(0, current);
}
}

/// <summary>
/// Orders given list of classes in a namespace based on inheritance.
/// That is, if class B extends class A then A should exported before class B.
/// </summary>
/// <param name="classes"> Classes in a given code namespace</param>
/// <returns> List of class names in the code name space ordered based on inheritance</returns>
private List<string> SortClassesInOrderOfInheritance(List<CodeClass> classes)
{
var visited = new HashSet<string>();
baywet marked this conversation as resolved.
Show resolved Hide resolved
var parentChildrenMap = new Dictionary<string, List<string>>();
var inheritanceOrderList = new List<string>();

/*
* 1. Create a dictionary containing all the parent classes.
*/
foreach (var @class in classes)
{
var usings = @class.StartBlock as CodeClass.Declaration;

// Verify if parent class is from the same namespace
baywet marked this conversation as resolved.
Show resolved Hide resolved
var inheritsFrom = usings?.Inherits?.TypeDefinition.Parent.Name == @class.Parent.Name ? usings.Inherits.Name : null;

if (inheritsFrom != null)
nikithauc marked this conversation as resolved.
Show resolved Hide resolved
{
if (!parentChildrenMap.ContainsKey(inheritsFrom))
{
parentChildrenMap[inheritsFrom] = new List<string>();
}
parentChildrenMap[inheritsFrom].Add(@class.Name);
baywet marked this conversation as resolved.
Show resolved Hide resolved

nikithauc marked this conversation as resolved.
Show resolved Hide resolved
}
}

/*
* 2. Print the export command for every parent node before the child node.
*/
foreach (var key in parentChildrenMap.Keys)
{
VisitEveryChild(parentChildrenMap, visited, inheritanceOrderList, key);
}

/*
* 3. Print all remaining classes which have not been visted or those do not have any parent/child relationship.
*/
foreach (var @class in classes)
baywet marked this conversation as resolved.
Show resolved Hide resolved
{
if (!visited.Contains(@class.Name))
{
visited.Add(@class.Name);
inheritanceOrderList.Add(@class.Name);
}
}
return inheritanceOrderList;
}
}
}
3 changes: 2 additions & 1 deletion src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Kiota.Builder.Writers.TypeScript
namespace Kiota.Builder.Writers.TypeScript
{
public class TypeScriptWriter : LanguageWriter
{
Expand All @@ -12,6 +12,7 @@ public TypeScriptWriter(string rootPath, string clientNamespaceName)
AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService));
AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService));
AddOrReplaceCodeElementWriter(new CodeTypeWriter(conventionService));
AddOrReplaceCodeElementWriter(new CodeNameSpaceWriter(conventionService));
}
}
}