diff --git a/CHANGELOG.md b/CHANGELOG.md index 07767373ee..f890bf3197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. @@ -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 diff --git a/src/Kiota.Builder/CodeDOM/CodeUsing.cs b/src/Kiota.Builder/CodeDOM/CodeUsing.cs index 79914605fc..95d6f51d9b 100644 --- a/src/Kiota.Builder/CodeDOM/CodeUsing.cs +++ b/src/Kiota.Builder/CodeDOM/CodeUsing.cs @@ -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() { @@ -25,6 +29,7 @@ public object Clone() Alias = Alias, Name = Name, Parent = Parent, + IsErasable = IsErasable, }; } } diff --git a/src/Kiota.Builder/Refiners/AdditionalUsingEvaluator.cs b/src/Kiota.Builder/Refiners/AdditionalUsingEvaluator.cs index 7346ecd92e..5c66ce54d1 100644 --- a/src/Kiota.Builder/Refiners/AdditionalUsingEvaluator.cs +++ b/src/Kiota.Builder/Refiners/AdditionalUsingEvaluator.cs @@ -1,9 +1,11 @@ using System; - using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Refiners; #pragma warning disable CA1819 -public record AdditionalUsingEvaluator(Func CodeElementEvaluator, string NamespaceName, params string[] ImportSymbols); +public record AdditionalUsingEvaluator(Func CodeElementEvaluator, string NamespaceName, bool IsErasable = false, params string[] ImportSymbols) +{ + public AdditionalUsingEvaluator(Func CodeElementEvaluator, string NamespaceName, params string[] ImportSymbols) : this(CodeElementEvaluator, NamespaceName, false, ImportSymbols) { } +} #pragma warning restore CA1819 diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 522274aa97..669b942c44 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -480,6 +480,7 @@ private static IEnumerable usingSelector(AdditionalUsingEvaluator x) { Name = y, Declaration = new CodeType { Name = x.NamespaceName, IsExternal = true }, + IsErasable = x.IsErasable, }); protected static void AddDefaultImports(CodeElement current, IEnumerable evaluators) { diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 2b54e97169..cceb01866e 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -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"; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeUsingWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeUsingWriter.cs index b856667a9a..8b7d4b9be1 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeUsingWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeUsingWriter.cs @@ -19,17 +19,17 @@ public void WriteCodeElement(IEnumerable 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(); } @@ -41,8 +41,8 @@ public void WriteCodeElement(IEnumerable 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) diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUsingWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUsingWriterTests.cs index 02515ff870..9699496181 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUsingWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeUsingWriterTests.cs @@ -66,7 +66,7 @@ public void DoesntAliasRegularSymbols() } [Fact] - public void WritesImportTypeStatement() + public void WritesImportTypeStatementForGeneratedInterfaces() { var usingWriter = new CodeUsingWriter("foo"); var someInterface = new CodeInterface @@ -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); + } }