Skip to content

Commit

Permalink
Merge pull request #3040 from microsoft/feature/typescript/use-import…
Browse files Browse the repository at this point in the history
…-type

Use ```import type``` for the typescript abstraction library code where relevant
  • Loading branch information
koros authored Aug 4, 2023
2 parents 7800428 + 7461e47 commit ff5e089
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 23 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added the title of the API in the VSCode tree view. [#2779](https://github.com/microsoft/kiota/issues/2779)
- Added capability to serialize and deserialize UUIDs in typescript[#40](https://github.com/microsoft/kiota-typescript/issues/40)
- Use `import type` statement if the generated code is an interface[#2959](https://github.com/microsoft/kiota/issues/2959)
- Added auto-generation header for class and enums in CSharp [#2886](https://github.com/microsoft/kiota/issues/2886)
- Added support for multipart form data request body in CSharp, Go, Java, and TypeScript. [#220](https://github.com/microsoft/kiota/issues/220)
- Added support for base64 encoded properties in TypeScript.
Expand All @@ -29,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `Method` to the list of `CSharpReservedClassNames`. [#2939](https://github.com/microsoft/kiota/pull/2939)
- Changed generation to use aggregate type for `TimeSpan` values in java [#2069](https://github.com/microsoft/kiota/issues/2069)
- Fixed generation of properties with identical names after symbol cleanup. [#2869](https://github.com/microsoft/kiota/issues/2869)
- Use `import type` syntax in Typescript for all code that will be arrased from output at runtime[#2959](https://github.com/microsoft/kiota/issues/2959)

## [1.4.0] - 2023-07-07

Expand Down
5 changes: 5 additions & 0 deletions src/Kiota.Builder/CodeDOM/CodeUsing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public bool IsExternal
{
get => Declaration?.IsExternal ?? true;
}
public bool IsErasable
{
get; set;
}
public string Alias { get; set; } = string.Empty;
public object Clone()
{
Expand All @@ -25,6 +29,7 @@ public object Clone()
Alias = Alias,
Name = Name,
Parent = Parent,
IsErasable = IsErasable,
};
}
}
6 changes: 4 additions & 2 deletions src/Kiota.Builder/Refiners/AdditionalUsingEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;

using Kiota.Builder.CodeDOM;

namespace Kiota.Builder.Refiners;

#pragma warning disable CA1819
public record AdditionalUsingEvaluator(Func<CodeElement, bool> CodeElementEvaluator, string NamespaceName, params string[] ImportSymbols);
public record AdditionalUsingEvaluator(Func<CodeElement, bool> CodeElementEvaluator, string NamespaceName, bool IsErasable = false, params string[] ImportSymbols)
{
public AdditionalUsingEvaluator(Func<CodeElement, bool> CodeElementEvaluator, string NamespaceName, params string[] ImportSymbols) : this(CodeElementEvaluator, NamespaceName, false, ImportSymbols) { }
}
#pragma warning restore CA1819
1 change: 1 addition & 0 deletions src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ private static IEnumerable<CodeUsing> usingSelector(AdditionalUsingEvaluator x)
{
Name = y,
Declaration = new CodeType { Name = x.NamespaceName, IsExternal = true },
IsErasable = x.IsErasable,
});
protected static void AddDefaultImports(CodeElement current, IEnumerable<AdditionalUsingEvaluator> evaluators)
{
Expand Down
41 changes: 29 additions & 12 deletions src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,31 +193,48 @@ currentInterface.StartBlock is InterfaceDeclaration interfaceDeclaration &&
}
private const string GuidPackageName = "guid-typescript";
private const string AbstractionsPackageName = "@microsoft/kiota-abstractions";
// A helper method to check if a parameter is a multipart body
private static bool IsMultipartBody(CodeParameter p) =>
p.IsOfKind(CodeParameterKind.RequestBody) &&
p.Type.Name.Equals(MultipartBodyClassName, StringComparison.OrdinalIgnoreCase);

// A helper method to check if a method has a multipart body parameter
private static bool HasMultipartBody(CodeMethod m) =>
m.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator) &&
m.Parameters.Any(IsMultipartBody);
// for Kiota abstration library if the code is not required for runtime purposes e.g. interfaces then the IsErassable flag is set to true
private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = {
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter),
AbstractionsPackageName, "RequestAdapter"),
AbstractionsPackageName, true, "RequestAdapter"),
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.Options),
AbstractionsPackageName, "RequestOption"),
AbstractionsPackageName, true, "RequestOption"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
AbstractionsPackageName, "HttpMethod", "RequestInformation", "RequestOption"),
AbstractionsPackageName, false, "HttpMethod", "RequestInformation"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
AbstractionsPackageName, true, "RequestOption"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer),
AbstractionsPackageName, "SerializationWriter"),
AbstractionsPackageName, true,"SerializationWriter"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Factory),
AbstractionsPackageName, "ParseNode"),
AbstractionsPackageName, true, "ParseNode"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility),
AbstractionsPackageName, "getPathParameters"),
AbstractionsPackageName, false, "getPathParameters"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor),
AbstractionsPackageName, "Parsable", "ParsableFactory"),
AbstractionsPackageName, true, "Parsable", "ParsableFactory"),
new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model),
AbstractionsPackageName, "Parsable"),
AbstractionsPackageName, true, "Parsable"),
new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && @class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)),
AbstractionsPackageName, "AdditionalDataHolder"),
AbstractionsPackageName, true, "AdditionalDataHolder"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) &&
method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)),
AbstractionsPackageName, true, "BackingStoreFactory"),
new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) &&
method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)),
AbstractionsPackageName, "BackingStoreFactory", "BackingStoreFactorySingleton"),
AbstractionsPackageName, false, "BackingStoreFactorySingleton"),
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore),
AbstractionsPackageName, true, "BackingStore", "BackedModel"),
new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.BackingStore),
AbstractionsPackageName, "BackingStore", "BackedModel", "BackingStoreFactorySingleton"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator) && method.Parameters.Any(static y => y.IsOfKind(CodeParameterKind.RequestBody) && y.Type.Name.Equals(MultipartBodyClassName, StringComparison.OrdinalIgnoreCase)),
AbstractionsPackageName, false, "BackingStoreFactorySingleton"),
new (x => x is CodeMethod m && HasMultipartBody(m),
AbstractionsPackageName, MultipartBodyClassName, $"serialize{MultipartBodyClassName}")
};
private const string MultipartBodyClassName = "MultipartBody";
Expand Down
14 changes: 7 additions & 7 deletions src/Kiota.Builder/Writers/TypeScript/CodeUsingWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public void WriteCodeElement(IEnumerable<CodeUsing> usings, CodeNamespace parent
var enumeratedUsings = usings.ToArray();
var externalImportSymbolsAndPaths = enumeratedUsings
.Where(static x => x.IsExternal)
.Select(static x => new { Symbol = x.Name, Alias = string.Empty, Path = x.Declaration?.Name ?? string.Empty, ShouldUseTypeImport = false });
.Select(static x => new { Symbol = x.Name, Alias = string.Empty, Path = x.Declaration?.Name ?? string.Empty, ShouldUseTypeImport = x.IsErasable });
var internalImportSymbolsAndPaths = enumeratedUsings
.Where(static x => !x.IsExternal)
.Select(x => new { CodeUsingPathTokens = _relativeImportManager.GetRelativeImportPathForUsing(x, parentNamespace), ShouldUseTypeImport = GetShouldUseTypeImport(x) });
var importSymbolsAndPaths = externalImportSymbolsAndPaths
.Union(internalImportSymbolsAndPaths.Select(static x => new { Symbol = x.CodeUsingPathTokens.Item1, Alias = x.CodeUsingPathTokens.Item2, Path = x.CodeUsingPathTokens.Item3, x.ShouldUseTypeImport }))
.GroupBy(static x => x.Path)
.OrderBy(static x => x.Key);
foreach (var codeUsing in importSymbolsAndPaths.Where(static x => !string.IsNullOrWhiteSpace(x.Key)))
.GroupBy(static x => (x.Path, x.ShouldUseTypeImport))
.OrderBy(static x => x.Key.Path);
foreach (var codeUsing in importSymbolsAndPaths.Where(static x => !string.IsNullOrWhiteSpace(x.Key.Path)))
writer.WriteLine($"import {codeUsing.Select(x => x.ShouldUseTypeImport ? "type " : "").Distinct().OrderBy(static x => x, StringComparer.Ordinal).Aggregate(static (x, y) => x)}" +
$"{{{codeUsing.Select(static x => GetAliasedSymbol(x.Symbol, x.Alias)).Distinct().OrderBy(static x => x, StringComparer.Ordinal).Aggregate(static (x, y) => x + ", " + y)}}} from '{codeUsing.Key}';");
$"{{{codeUsing.Select(static x => GetAliasedSymbol(x.Symbol, x.Alias)).Distinct().OrderBy(static x => x, StringComparer.Ordinal).Aggregate(static (x, y) => x + ", " + y)}}} from '{codeUsing.Key.Path}';");

writer.WriteLine();
}
Expand All @@ -41,8 +41,8 @@ public void WriteCodeElement(IEnumerable<CodeUsing> usings, CodeNamespace parent
**/
private static bool GetShouldUseTypeImport(CodeUsing codeUsing)
{
// Check if codeUsing.Declaration is an instance of CodeType and if codeType.TypeDefinition is an instance of CodeInterface
return codeUsing.Declaration is CodeType codeType && codeType.TypeDefinition is CodeInterface;
// Check if codeUsing is Erassable or codeUsing.Declaration is an instance of CodeType and if codeType.TypeDefinition is an instance of CodeInterface
return codeUsing.IsErasable || codeUsing.Declaration is CodeType codeType && codeType.TypeDefinition is CodeInterface;
}

private static string GetAliasedSymbol(string symbol, string alias)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void DoesntAliasRegularSymbols()
}

[Fact]
public void WritesImportTypeStatement()
public void WritesImportTypeStatementForGeneratedInterfaces()
{
var usingWriter = new CodeUsingWriter("foo");
var someInterface = new CodeInterface
Expand All @@ -88,4 +88,27 @@ public void WritesImportTypeStatement()
var result = tw.ToString();
Assert.Contains("import type {Bar} from", result);
}

[Fact]
public void WritesImportTypeStatementForDenotedExternalLibraries()
{
var usingWriter = new CodeUsingWriter("foo");
var codeClass = root.AddClass(new CodeClass
{
Name = "bar",
}).First();
var us = new CodeUsing
{
Name = "bar",
Declaration = new CodeType
{
Name = codeClass.Name,
TypeDefinition = codeClass,
},
IsErasable = true,
};
usingWriter.WriteCodeElement(new CodeUsing[] { us }, root, writer);
var result = tw.ToString();
Assert.Contains("import type {Bar} from", result);
}
}

0 comments on commit ff5e089

Please sign in to comment.