From 3d94bc3d7eb82b002b5aac7983a78aa4a005e173 Mon Sep 17 00:00:00 2001 From: Dapeng Zhang Date: Thu, 25 Jun 2026 18:19:29 +0800 Subject: [PATCH 1/3] fix(csharp): build public reference map from providers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/CSharpGen.cs | 2 +- .../PostProcessing/GeneratedCodeWorkspace.cs | 9 +- .../src/PostProcessing/PostProcessor.cs | 13 +- .../src/PostProcessing/ReferenceMapBuilder.cs | 217 ++++++++++++++++++ .../test/PostProcessing/PostProcessorTests.cs | 107 +++++++++ 5 files changed, 337 insertions(+), 11 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs index c013817a72e..9cc83d37292 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs @@ -111,7 +111,7 @@ await GeneratedCodeWorkspace.LoadBaselineContract(), LoggingHelpers.LogElapsedTime("All old generated files have been deleted"); - await generatedCodeWorkspace.PostProcessAsync(); + await generatedCodeWorkspace.PostProcessAsync(output.TypeProviders); // Write the generated files to the output directory await foreach (var file in generatedCodeWorkspace.GetGeneratedFilesAsync()) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs index c36686f637f..b4a2a46157e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs @@ -262,9 +262,9 @@ internal static Project AddDirectory(Project project, string directory, Func /// This method invokes the postProcessor to do some post processing work - /// Depending on the configuration, it will either remove + internalize, just internalize or do nothing + /// Depending on the configuration, it will either internalize or do nothing /// - public async Task PostProcessAsync() + public async Task PostProcessAsync(IReadOnlyList typeProviders) { var modelFactory = CodeModelGenerator.Instance.OutputLibrary.ModelFactory.Value; var nonRootTypes = CodeModelGenerator.Instance.NonRootTypes; @@ -278,11 +278,10 @@ public async Task PostProcessAsync() case Configuration.UnreferencedTypesHandlingOption.KeepAll: break; case Configuration.UnreferencedTypesHandlingOption.Internalize: - _project = await postProcessor.InternalizeAsync(_project); + _project = await postProcessor.InternalizeAsync(_project, typeProviders); break; case Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize: - _project = await postProcessor.InternalizeAsync(_project); - _project = await postProcessor.RemoveAsync(_project); + _project = await postProcessor.InternalizeAsync(_project, typeProviders); break; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs index dc42f801732..09ddfbe898b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.TypeSpec.Generator.Providers; namespace Microsoft.TypeSpec.Generator { @@ -123,7 +124,7 @@ protected virtual bool ShouldIncludeDocument(Document document) => /// /// The project to process /// The processed . is immutable, therefore this should usually be a new instance - public async Task InternalizeAsync(Project project) + public async Task InternalizeAsync(Project project, IReadOnlyList? typeProviders = null) { var compilation = await project.GetCompilationAsync(); if (compilation == null) @@ -133,12 +134,14 @@ public async Task InternalizeAsync(Project project) // first get all the declared symbols var definitions = await GetTypeSymbolsAsync(compilation, project, true); - // build the reference map - var referenceMap = - await new ReferenceMapBuilder(compilation, project).BuildPublicReferenceMapAsync( - definitions.DeclaredSymbols, definitions.DeclaredNodesCache); // get the root symbols var rootSymbols = await GetRootSymbolsAsync(project, definitions); + // build the reference map + var referenceMap = typeProviders == null + ? await new ReferenceMapBuilder(compilation, project).BuildPublicReferenceMapAsync( + definitions.DeclaredSymbols, definitions.DeclaredNodesCache) + : new ReferenceMapBuilder(compilation, project).BuildPublicReferenceMap( + typeProviders, rootSymbols, definitions.DeclaredSymbols); // traverse all the root and recursively add all the things we met var publicSymbols = VisitSymbolsFromRootAsync(rootSymbols, referenceMap); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs index a4b1e31f90b..7ab200a324d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs @@ -10,6 +10,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; namespace Microsoft.TypeSpec.Generator { @@ -46,6 +48,69 @@ public async Task BuildAllReferenceMapAsync(IEnumerable providers, + IEnumerable rootSymbols, + IEnumerable definitions) + { + var referenceMap = new ReferenceMap(); + var definitionSymbols = definitions.ToArray(); + var symbolByProvider = BuildProviderSymbolMap(providers, definitionSymbols); + var providerBySymbol = new Dictionary(SymbolEqualityComparer.Default); + foreach (var (provider, symbol) in symbolByProvider) + { + providerBySymbol.TryAdd(symbol, provider); + } + + var queue = new Queue(); + foreach (var rootSymbol in rootSymbols) + { + if (providerBySymbol.TryGetValue(rootSymbol, out var provider)) + { + queue.Enqueue(provider); + } + } + + var visited = new HashSet(); + while (queue.Count > 0) + { + var provider = queue.Dequeue(); + if (!visited.Add(provider)) + { + continue; + } + + var canonicalView = provider.CanonicalView; + if (!symbolByProvider.TryGetValue(canonicalView, out var providerSymbol)) + { + continue; + } + + var referencedSymbols = ProcessPublicApi(canonicalView, providerSymbol, referenceMap, definitionSymbols); + foreach (var referencedSymbol in referencedSymbols) + { + if (providerBySymbol.TryGetValue(referencedSymbol, out var referencedProvider)) + { + queue.Enqueue(referencedProvider); + } + } + + foreach (var (derivedProvider, derivedSymbol) in symbolByProvider) + { + var baseSymbol = derivedProvider.BaseType == null ? null : ResolveType(derivedProvider.BaseType, definitionSymbols); + if (!SymbolEqualityComparer.Default.Equals(baseSymbol, providerSymbol)) + { + continue; + } + + referenceMap.AddInList(providerSymbol, derivedSymbol); + queue.Enqueue(derivedProvider); + } + } + + return referenceMap; + } + private async Task ProcessPublicSymbolAsync(INamedTypeSymbol symbol, ReferenceMap referenceMap, IReadOnlyDictionary> cache) { // only add to reference when myself is public @@ -206,6 +271,158 @@ private async Task AddReferenceToReferenceMapAsync(INamedTypeSymbol symbol, Refe } } + private static IReadOnlyDictionary BuildProviderSymbolMap( + IEnumerable providers, + IReadOnlyList definitions) + { + var result = new Dictionary(); + foreach (var provider in GetProviders(providers)) + { + var canonicalView = provider.CanonicalView; + var symbol = ResolveType(canonicalView.Type, definitions); + if (symbol != null) + { + result[canonicalView] = symbol; + if (!ReferenceEquals(provider, canonicalView)) + { + result[provider] = symbol; + } + } + } + + return result; + } + + private static IEnumerable GetProviders(IEnumerable providers) + { + foreach (var provider in providers) + { + yield return provider; + foreach (var nestedType in GetProviders(provider.CanonicalView.NestedTypes)) + { + yield return nestedType; + } + } + } + + private static IEnumerable ProcessPublicApi( + TypeProvider provider, + INamedTypeSymbol providerSymbol, + ReferenceMap referenceMap, + IReadOnlyList definitions) + { + var referencedSymbols = new HashSet(SymbolEqualityComparer.Default); + AddType(providerSymbol, provider.Type, referenceMap, definitions, referencedSymbols); + AddType(providerSymbol, provider.BaseType, referenceMap, definitions, referencedSymbols); + foreach (var implementedType in provider.Implements) + { + AddType(providerSymbol, implementedType, referenceMap, definitions, referencedSymbols); + } + + foreach (var constructor in provider.Constructors.Where(static c => IsPublicApi(c.Signature.Modifiers))) + { + ProcessMethodSignature(providerSymbol, constructor.Signature, referenceMap, definitions, referencedSymbols); + } + + foreach (var method in provider.Methods.Where(static m => IsPublicApi(m.Signature.Modifiers))) + { + ProcessMethodSignature(providerSymbol, method.Signature, referenceMap, definitions, referencedSymbols); + AddType(providerSymbol, method.Signature.ExplicitInterface, referenceMap, definitions, referencedSymbols); + foreach (var genericArgument in method.Signature.GenericArguments ?? []) + { + AddType(providerSymbol, genericArgument, referenceMap, definitions, referencedSymbols); + } + } + + foreach (var property in provider.Properties.Where(static p => IsPublicApi(p.Modifiers))) + { + AddType(providerSymbol, property.Type, referenceMap, definitions, referencedSymbols); + AddType(providerSymbol, property.ExplicitInterface, referenceMap, definitions, referencedSymbols); + } + + foreach (var field in provider.Fields.Where(static f => IsPublicApi(f.Modifiers))) + { + AddType(providerSymbol, field.Type, referenceMap, definitions, referencedSymbols); + } + + foreach (var nestedType in provider.NestedTypes.Where(static t => IsPublicApi(t.DeclarationModifiers))) + { + AddType(providerSymbol, nestedType.Type, referenceMap, definitions, referencedSymbols); + } + + return referencedSymbols; + } + + private static void ProcessMethodSignature( + INamedTypeSymbol keySymbol, + MethodSignatureBase signature, + ReferenceMap referenceMap, + IReadOnlyList definitions, + HashSet referencedSymbols) + { + AddType(keySymbol, signature.ReturnType, referenceMap, definitions, referencedSymbols); + foreach (var parameter in signature.Parameters) + { + AddType(keySymbol, parameter.Type, referenceMap, definitions, referencedSymbols); + } + } + + private static void AddType( + INamedTypeSymbol keySymbol, + CSharpType? type, + ReferenceMap referenceMap, + IReadOnlyList definitions, + HashSet referencedSymbols) + { + if (type == null) + { + return; + } + + var valueSymbol = ResolveType(type, definitions); + if (valueSymbol != null && referenceMap.AddInList(keySymbol, valueSymbol)) + { + referencedSymbols.Add(valueSymbol); + } + + AddType(keySymbol, type.BaseType, referenceMap, definitions, referencedSymbols); + AddType(keySymbol, type.DeclaringType, referenceMap, definitions, referencedSymbols); + foreach (var argument in type.Arguments) + { + AddType(keySymbol, argument, referenceMap, definitions, referencedSymbols); + } + } + + private static INamedTypeSymbol? ResolveType(CSharpType type, IReadOnlyList definitions) + { + if (type.IsFrameworkType) + { + return null; + } + + foreach (var definition in definitions) + { + if (definition.IsSameType(type)) + { + return definition; + } + } + + return null; + } + + private static bool IsPublicApi(MethodSignatureModifiers modifiers) + => (modifiers.HasFlag(MethodSignatureModifiers.Public) || modifiers.HasFlag(MethodSignatureModifiers.Protected)) + && !modifiers.HasFlag(MethodSignatureModifiers.Private); + + private static bool IsPublicApi(FieldModifiers modifiers) + => (modifiers.HasFlag(FieldModifiers.Public) || modifiers.HasFlag(FieldModifiers.Protected)) + && !modifiers.HasFlag(FieldModifiers.Private); + + private static bool IsPublicApi(TypeSignatureModifiers modifiers) + => (modifiers.HasFlag(TypeSignatureModifiers.Public) || modifiers.HasFlag(TypeSignatureModifiers.Protected)) + && !modifiers.HasFlag(TypeSignatureModifiers.Private); + /// /// This method recursively adds all related types in to the reference map as the value of key /// diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs index 28981148a4d..e478ee98d40 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs @@ -7,7 +7,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -289,6 +293,78 @@ public async Task DoesNotRemoveValidAttributes() Assert.AreEqual(Helpers.GetExpectedFromFile().TrimEnd(), output, "The output should match the expected content."); } + [Test] + public async Task InternalizeUsesProviderPublicApiReferences() + { + MockHelpers.LoadMockGenerator(); + + var request = new TestTypeProvider("RequestBody", Path.Combine("src", "Generated", "Models", "RequestBody.cs")); + var dependency = new TestTypeProvider("Dependency", Path.Combine("src", "Generated", "Models", "Dependency.cs")); + var response = new TestTypeProvider("ResponseBody", Path.Combine("src", "Generated", "Models", "ResponseBody.cs")); + response.PropertiesToBuild.Add(new PropertyProvider( + null, + MethodSignatureModifiers.Public, + dependency.Type, + "Dependency", + new AutoPropertyBody(false), + response)); + + var client = new TestTypeProvider("SampleClient", Path.Combine("src", "Generated", "SampleClient.cs")); + client.FieldsToBuild.Add(new FieldProvider(FieldModifiers.Private, request.Type, "_request", client)); + client.MethodsToBuild.Add(new MethodProvider( + new MethodSignature("Get", null, MethodSignatureModifiers.Public, response.Type, null, []), + MethodBodyStatement.Empty, + client)); + + var providers = new[] { client, request, response, dependency }; + var project = CreateProject(providers); + + var postProcessor = new PostProcessor([]); + var resultProject = await postProcessor.InternalizeAsync(project, providers); + + AssertTypeAccessibility(resultProject, "SampleClient", SyntaxKind.PublicKeyword); + AssertTypeAccessibility(resultProject, "ResponseBody", SyntaxKind.PublicKeyword); + AssertTypeAccessibility(resultProject, "Dependency", SyntaxKind.PublicKeyword); + AssertTypeAccessibility(resultProject, "RequestBody", SyntaxKind.InternalKeyword); + } + + private static Project CreateProject(IEnumerable providers) + { + var workspace = new AdhocWorkspace(); + var projectInfo = ProjectInfo.Create( + ProjectId.CreateNewId(), + VersionStamp.Create(), + name: "TestProj", + assemblyName: "TestProj", + language: LanguageNames.CSharp) + .WithMetadataReferences(new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location) + }); + + var project = workspace.AddProject(projectInfo); + foreach (var provider in providers) + { + var codeFile = new TypeProviderWriter(provider).Write(); + project = project.AddDocument(codeFile.Name, codeFile.Content, ["Generated"]).Project; + } + + return project; + } + + private static void AssertTypeAccessibility(Project project, string typeName, SyntaxKind expectedAccessibility) + { + var type = project.Documents + .Select(document => document.GetSyntaxRootAsync().Result) + .Where(root => root != null) + .SelectMany(root => root!.DescendantNodes().OfType()) + .Single(t => t.Identifier.Text == typeName); + + Assert.IsTrue( + type.Modifiers.Any(expectedAccessibility), + $"Expected {typeName} to have {expectedAccessibility}."); + } + private class TestPostProcessor : PostProcessor { private readonly string _rootFile; @@ -303,5 +379,36 @@ protected override Task IsRootDocument(Document document) return document.Name == _rootFile ? Task.FromResult(true) : Task.FromResult(false); } } + + private class TestTypeProvider : TypeProvider + { + private readonly string _name; + private readonly string _relativeFilePath; + + public TestTypeProvider(string name, string relativeFilePath) + { + _name = name; + _relativeFilePath = relativeFilePath; + } + + public List FieldsToBuild { get; } = []; + public List MethodsToBuild { get; } = []; + public List PropertiesToBuild { get; } = []; + + protected override string BuildName() => _name; + + protected override string BuildNamespace() => "Sample"; + + protected override string BuildRelativeFilePath() => _relativeFilePath; + + protected override TypeSignatureModifiers BuildDeclarationModifiers() + => TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class; + + protected internal override FieldProvider[] BuildFields() => [.. FieldsToBuild]; + + protected internal override MethodProvider[] BuildMethods() => [.. MethodsToBuild]; + + protected internal override PropertyProvider[] BuildProperties() => [.. PropertiesToBuild]; + } } } From 49d55982a7276bb517e073c300a55256236f9820 Mon Sep 17 00:00:00 2001 From: Dapeng Zhang Date: Thu, 25 Jun 2026 18:47:38 +0800 Subject: [PATCH 2/3] fix(csharp): internalize providers before writing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/http-client-csharp/docs/emitter.md | 4 +- .../http-client-csharp/emitter/src/options.ts | 6 +- .../emitter/src/type/configuration.ts | 2 +- .../emitter/test/Unit/options.test.ts | 4 +- .../src/CSharpGen.cs | 30 ++- .../src/Configuration.cs | 9 +- .../PostProcessing/GeneratedCodeWorkspace.cs | 26 --- .../src/PostProcessing/PostProcessor.cs | 100 +++++++- .../ProviderReferenceMapBuilder.cs | 175 ++++++++++++++ .../src/PostProcessing/ReferenceMapBuilder.cs | 217 ------------------ .../test/ConfigurationTests.cs | 2 - .../test/PostProcessing/PostProcessorTests.cs | 66 ++---- .../test/StartUp/GeneratorHandlerTests.cs | 10 +- packages/http-client-csharp/readme.md | 4 +- 14 files changed, 329 insertions(+), 326 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ProviderReferenceMapBuilder.cs diff --git a/packages/http-client-csharp/docs/emitter.md b/packages/http-client-csharp/docs/emitter.md index 827da57427a..1cea78a066e 100644 --- a/packages/http-client-csharp/docs/emitter.md +++ b/packages/http-client-csharp/docs/emitter.md @@ -56,9 +56,9 @@ Set to `false` to skip generation of convenience methods. The default value is ` ### `unreferenced-types-handling` -**Type:** `"removeOrInternalize" | "internalize" | "keepAll"` +**Type:** `"internalize" | "keepAll"` -Defines the strategy on how to handle unreferenced types. The default value is `removeOrInternalize`. +Defines the strategy on how to handle unreferenced types. The default value is `internalize`. ### `new-project` diff --git a/packages/http-client-csharp/emitter/src/options.ts b/packages/http-client-csharp/emitter/src/options.ts index 6978e355f9b..4d3df57e8bf 100644 --- a/packages/http-client-csharp/emitter/src/options.ts +++ b/packages/http-client-csharp/emitter/src/options.ts @@ -15,7 +15,7 @@ type ApiVersionSelection = string | Record; export interface CSharpEmitterOptions { "api-version"?: ApiVersionSelection; - "unreferenced-types-handling"?: "removeOrInternalize" | "internalize" | "keepAll"; + "unreferenced-types-handling"?: "internalize" | "keepAll"; "new-project"?: boolean; "save-inputs"?: boolean; debug?: boolean; @@ -61,10 +61,10 @@ export const CSharpEmitterOptionsSchema: JSONSchemaType = }, "unreferenced-types-handling": { type: "string", - enum: ["removeOrInternalize", "internalize", "keepAll"], + enum: ["internalize", "keepAll"], nullable: true, description: - "Defines the strategy on how to handle unreferenced types. The default value is `removeOrInternalize`.", + "Defines the strategy on how to handle unreferenced types. The default value is `internalize`.", }, "new-project": { type: "boolean", diff --git a/packages/http-client-csharp/emitter/src/type/configuration.ts b/packages/http-client-csharp/emitter/src/type/configuration.ts index 2a2f3e2f2e8..11bce6cde71 100644 --- a/packages/http-client-csharp/emitter/src/type/configuration.ts +++ b/packages/http-client-csharp/emitter/src/type/configuration.ts @@ -3,7 +3,7 @@ export interface Configuration { "package-name": string | null; - "unreferenced-types-handling"?: "removeOrInternalize" | "internalize" | "keepAll"; + "unreferenced-types-handling"?: "internalize" | "keepAll"; "disable-xml-docs"?: boolean; "disable-roslyn-reduce"?: boolean; license?: { diff --git a/packages/http-client-csharp/emitter/test/Unit/options.test.ts b/packages/http-client-csharp/emitter/test/Unit/options.test.ts index be8603adeb0..9f62fae3d5a 100644 --- a/packages/http-client-csharp/emitter/test/Unit/options.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/options.test.ts @@ -124,7 +124,7 @@ describe("Configuration tests", async () => { } const customOptions: TestEmitterOptions = { "package-name": "custom-package", - "unreferenced-types-handling": "removeOrInternalize", + "unreferenced-types-handling": "internalize", "disable-xml-docs": true, "disable-roslyn-reduce": true, license: { @@ -141,7 +141,7 @@ describe("Configuration tests", async () => { const config = createConfiguration(customOptions, "rootNamespace", sdkContext); expect(config["package-name"]).toBe("custom-package"); - expect(config["unreferenced-types-handling"]).toBe("removeOrInternalize"); + expect(config["unreferenced-types-handling"]).toBe("internalize"); expect(config["disable-xml-docs"]).toBe(true); expect(config["disable-roslyn-reduce"]).toBe(true); expect(config.license).toEqual({ diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs index 9cc83d37292..da0b80a9409 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CSharpGen.cs @@ -90,6 +90,19 @@ await GeneratedCodeWorkspace.LoadBaselineContract(), { // Ensure back-compatibility processing is done after all visitors have run outputType.ProcessTypeForBackCompatibility(); + } + + PostProcessTypeProviders(output.TypeProviders); + + LoggingHelpers.LogElapsedTime("All generated type providers post-processed"); + + var modelFactory = output.ModelFactory.Value; + foreach (var outputType in output.TypeProviders) + { + if (ReferenceEquals(outputType, modelFactory) && outputType.Methods.Count == 0) + { + continue; + } var writer = CodeModelGenerator.Instance.GetWriter(outputType); generateFilesTasks.Add(generatedCodeWorkspace.AddGeneratedFile(writer.Write())); @@ -111,8 +124,6 @@ await GeneratedCodeWorkspace.LoadBaselineContract(), LoggingHelpers.LogElapsedTime("All old generated files have been deleted"); - await generatedCodeWorkspace.PostProcessAsync(output.TypeProviders); - // Write the generated files to the output directory await foreach (var file in generatedCodeWorkspace.GetGeneratedFilesAsync()) { @@ -138,6 +149,21 @@ await GeneratedCodeWorkspace.LoadBaselineContract(), LoggingHelpers.LogElapsedTime("All files have been written to disk"); } + private static void PostProcessTypeProviders(IReadOnlyList typeProviders) + { + if (Configuration.UnreferencedTypesHandling == Configuration.UnreferencedTypesHandlingOption.KeepAll) + { + return; + } + + var modelFactory = CodeModelGenerator.Instance.OutputLibrary.ModelFactory.Value; + var postProcessor = new PostProcessor( + [.. CodeModelGenerator.Instance.TypeFactory.UnionVariantTypesToKeep, .. CodeModelGenerator.Instance.AdditionalRootTypes], + modelFactoryFullName: modelFactory.Type.FullyQualifiedName, + additionalNonRootTypeNames: CodeModelGenerator.Instance.NonRootTypes); + postProcessor.Internalize(typeProviders); + } + internal static void FilterAllCustomizedMembers(OutputLibrary output) { foreach (var typeProvider in output.TypeProviders) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Configuration.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Configuration.cs index 39ddf41b3ec..663e26c103a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Configuration.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Configuration.cs @@ -15,9 +15,8 @@ public class Configuration { public enum UnreferencedTypesHandlingOption { - RemoveOrInternalize = 0, - Internalize = 1, - KeepAll = 2 + Internalize = 0, + KeepAll = 1 } private const string GeneratedFolderName = "Generated"; @@ -83,7 +82,7 @@ private static class Options /// public LicenseInfo? LicenseInfo { get; } - internal static UnreferencedTypesHandlingOption UnreferencedTypesHandling { get; private set; } = UnreferencedTypesHandlingOption.RemoveOrInternalize; + internal static UnreferencedTypesHandlingOption UnreferencedTypesHandling { get; private set; } = UnreferencedTypesHandlingOption.Internalize; private string? _projectDirectory; internal string ProjectDirectory => _projectDirectory ??= Path.Combine(OutputDirectory, "src"); @@ -253,7 +252,7 @@ private static T ReadEnumOption(JsonElement root, string option) where T : st public static Enum? GetDefaultEnumOptionValue(string option) => option switch { - Options.UnreferencedTypesHandling => UnreferencedTypesHandlingOption.RemoveOrInternalize, + Options.UnreferencedTypesHandling => UnreferencedTypesHandlingOption.Internalize, _ => null }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs index b4a2a46157e..64d8f664e19 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/GeneratedCodeWorkspace.cs @@ -260,32 +260,6 @@ internal static Project AddDirectory(Project project, string directory, Func - /// This method invokes the postProcessor to do some post processing work - /// Depending on the configuration, it will either internalize or do nothing - /// - public async Task PostProcessAsync(IReadOnlyList typeProviders) - { - var modelFactory = CodeModelGenerator.Instance.OutputLibrary.ModelFactory.Value; - var nonRootTypes = CodeModelGenerator.Instance.NonRootTypes; - var postProcessor = new PostProcessor( - [.. CodeModelGenerator.Instance.TypeFactory.UnionVariantTypesToKeep, .. CodeModelGenerator.Instance.AdditionalRootTypes], - modelFactoryFullName: modelFactory.Type.FullyQualifiedName, - additionalNonRootTypeNames: nonRootTypes); - - switch (Configuration.UnreferencedTypesHandling) - { - case Configuration.UnreferencedTypesHandlingOption.KeepAll: - break; - case Configuration.UnreferencedTypesHandlingOption.Internalize: - _project = await postProcessor.InternalizeAsync(_project, typeProviders); - break; - case Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize: - _project = await postProcessor.InternalizeAsync(_project, typeProviders); - break; - } - } - /// /// Resolves PackageReference items from the project's .csproj file and adds their assemblies /// as metadata references so that custom code referencing external NuGet types compiles correctly. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs index 09ddfbe898b..bdf7913eeea 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; namespace Microsoft.TypeSpec.Generator @@ -114,6 +115,95 @@ private async Task GetTypeSymbolsAsync(Compilation compilation, protected virtual bool ShouldIncludeDocument(Document document) => !GeneratedCodeWorkspace.IsGeneratedTestDocument(document); + public void Internalize(IReadOnlyList typeProviders) + { + var allProviders = ProviderReferenceMapBuilder.GetAllProviders(typeProviders).ToArray(); + var candidateProviders = allProviders + .Where(IsPublicType) + .Where(provider => !IsExcludedProvider(provider)) + .ToArray(); + var rootProviders = candidateProviders.Where(IsRootProvider).ToArray(); + var referenceMap = new ProviderReferenceMapBuilder(typeProviders).BuildPublicReferenceMap(rootProviders); + var referencedProviders = VisitProvidersFromRoot(rootProviders, referenceMap).ToHashSet(); + var providersToInternalize = candidateProviders + .Where(provider => !referencedProviders.Contains(provider)) + .ToArray(); + + foreach (var provider in providersToInternalize) + { + provider.Update(modifiers: MakeInternal(provider.DeclarationModifiers)); + } + + RemoveMethodsFromModelFactory(providersToInternalize.Select(provider => provider.Name).ToHashSet()); + } + + private bool IsRootProvider(TypeProvider provider) + => IsClientProvider(provider) || ShouldKeepProvider(provider, _typesToKeep); + + private bool IsExcludedProvider(TypeProvider provider) + => IsModelFactoryProvider(provider) || ShouldKeepProvider(provider, _additionalNonRootTypeNames); + + private bool IsModelFactoryProvider(TypeProvider provider) + => _modelFactoryFullName != null && provider.Type.FullyQualifiedName == _modelFactoryFullName; + + private static bool IsClientProvider(TypeProvider provider) + => provider.Name.EndsWith("Client", StringComparison.Ordinal); + + private static bool ShouldKeepProvider(TypeProvider provider, HashSet typesToKeep) + => typesToKeep.Contains(provider.Name) || typesToKeep.Contains(provider.Type.FullyQualifiedName); + + private static bool IsPublicType(TypeProvider provider) + => provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public); + + private static TypeSignatureModifiers MakeInternal(TypeSignatureModifiers modifiers) + => (modifiers & ~(TypeSignatureModifiers.Public | TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected)) | TypeSignatureModifiers.Internal; + + private static IEnumerable VisitProvidersFromRoot( + IEnumerable rootProviders, + IReadOnlyDictionary> referenceMap) + { + var queue = new Queue(rootProviders); + var visited = new HashSet(); + while (queue.Count > 0) + { + var provider = queue.Dequeue(); + if (!visited.Add(provider)) + { + continue; + } + + yield return provider; + if (!referenceMap.TryGetValue(provider, out var references)) + { + continue; + } + + foreach (var reference in references) + { + queue.Enqueue(reference); + } + } + } + + private void RemoveMethodsFromModelFactory(HashSet namesToRemove) + { + if (_modelFactoryFullName == null || namesToRemove.Count == 0) + { + return; + } + + var modelFactory = CodeModelGenerator.Instance.OutputLibrary.ModelFactory.Value; + if (modelFactory.Type.FullyQualifiedName != _modelFactoryFullName) + { + return; + } + + var methodsToKeep = modelFactory.Methods + .Where(method => !namesToRemove.Contains(method.Signature.Name)) + .ToArray(); + modelFactory.Update(methods: methodsToKeep); + } + /// /// This method marks the "not publicly" referenced types as internal if they are previously defined as public. It will do this job in the following steps: /// 1. This method will read all the public types defined in the given , and build a cache for those symbols @@ -124,7 +214,7 @@ protected virtual bool ShouldIncludeDocument(Document document) => /// /// The project to process /// The processed . is immutable, therefore this should usually be a new instance - public async Task InternalizeAsync(Project project, IReadOnlyList? typeProviders = null) + public async Task InternalizeAsync(Project project) { var compilation = await project.GetCompilationAsync(); if (compilation == null) @@ -137,11 +227,9 @@ public async Task InternalizeAsync(Project project, IReadOnlyList _providers; + + public ProviderReferenceMapBuilder(IReadOnlyList providers) + { + _providers = [.. GetAllProviders(providers)]; + } + + public IReadOnlyDictionary> BuildPublicReferenceMap(IEnumerable rootTypes) + { + var referenceMap = new Dictionary>(); + var visited = new HashSet(); + var queue = new Queue(rootTypes); + + while (queue.Count > 0) + { + var provider = queue.Dequeue(); + if (!visited.Add(provider)) + { + continue; + } + + var referencedTypes = BuildPublicApiReferences(provider.CanonicalView); + referenceMap[provider] = referencedTypes; + foreach (var referencedType in referencedTypes) + { + queue.Enqueue(referencedType); + } + } + + return referenceMap; + } + + public static IEnumerable GetAllProviders(IEnumerable providers) + { + foreach (var provider in providers) + { + yield return provider; + + foreach (var nestedType in GetAllProviders(provider.CanonicalView.NestedTypes)) + { + yield return nestedType; + } + + foreach (var serializationProvider in provider.SerializationProviders) + { + yield return serializationProvider; + } + } + } + + private IReadOnlyList BuildPublicApiReferences(TypeProvider provider) + { + var referencedTypes = new HashSet(); + + AddType(provider.Type, referencedTypes); + AddType(provider.BaseType, referencedTypes); + foreach (var implementedType in provider.Implements) + { + AddType(implementedType, referencedTypes); + } + + foreach (var constructor in provider.Constructors.Where(static c => IsPublicApi(c.Signature.Modifiers))) + { + AddSignatureTypes(constructor.Signature, referencedTypes); + } + + foreach (var method in provider.Methods.Where(static m => IsPublicApi(m.Signature.Modifiers))) + { + AddSignatureTypes(method.Signature, referencedTypes); + AddType(method.Signature.ExplicitInterface, referencedTypes); + foreach (var genericArgument in method.Signature.GenericArguments ?? []) + { + AddType(genericArgument, referencedTypes); + } + } + + foreach (var property in provider.Properties.Where(static p => IsPublicApi(p.Modifiers))) + { + AddType(property.Type, referencedTypes); + AddType(property.ExplicitInterface, referencedTypes); + } + + foreach (var field in provider.Fields.Where(static f => IsPublicApi(f.Modifiers))) + { + AddType(field.Type, referencedTypes); + } + + foreach (var nestedType in provider.NestedTypes.Where(static t => IsPublicApi(t.DeclarationModifiers))) + { + AddType(nestedType.Type, referencedTypes); + } + + return [.. referencedTypes]; + } + + private void AddSignatureTypes(MethodSignatureBase signature, HashSet referencedTypes) + { + AddType(signature.ReturnType, referencedTypes); + foreach (var parameter in signature.Parameters) + { + AddType(parameter.Type, referencedTypes); + } + } + + private void AddType(CSharpType? type, HashSet referencedTypes) + { + if (type == null) + { + return; + } + + var provider = ResolveType(type); + if (provider != null) + { + referencedTypes.Add(provider); + } + + AddType(type.BaseType, referencedTypes); + AddType(type.DeclaringType, referencedTypes); + foreach (var argument in type.Arguments) + { + AddType(argument, referencedTypes); + } + + if (type.IsUnion) + { + foreach (var unionItemType in type.UnionItemTypes) + { + AddType(unionItemType, referencedTypes); + } + } + } + + private TypeProvider? ResolveType(CSharpType type) + { + if (type.IsFrameworkType) + { + return null; + } + + if (CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && provider != null) + { + return provider; + } + + return _providers.FirstOrDefault(provider => + provider.Type.AreNamesEqual(type) || provider.CanonicalView.Type.AreNamesEqual(type)); + } + + private static bool IsPublicApi(MethodSignatureModifiers modifiers) + => (modifiers.HasFlag(MethodSignatureModifiers.Public) || modifiers.HasFlag(MethodSignatureModifiers.Protected)) + && !modifiers.HasFlag(MethodSignatureModifiers.Private); + + private static bool IsPublicApi(FieldModifiers modifiers) + => (modifiers.HasFlag(FieldModifiers.Public) || modifiers.HasFlag(FieldModifiers.Protected)) + && !modifiers.HasFlag(FieldModifiers.Private); + + private static bool IsPublicApi(TypeSignatureModifiers modifiers) + => (modifiers.HasFlag(TypeSignatureModifiers.Public) || modifiers.HasFlag(TypeSignatureModifiers.Protected)) + && !modifiers.HasFlag(TypeSignatureModifiers.Private); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs index 7ab200a324d..a4b1e31f90b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ReferenceMapBuilder.cs @@ -10,8 +10,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.TypeSpec.Generator.Primitives; -using Microsoft.TypeSpec.Generator.Providers; namespace Microsoft.TypeSpec.Generator { @@ -48,69 +46,6 @@ public async Task BuildAllReferenceMapAsync(IEnumerable providers, - IEnumerable rootSymbols, - IEnumerable definitions) - { - var referenceMap = new ReferenceMap(); - var definitionSymbols = definitions.ToArray(); - var symbolByProvider = BuildProviderSymbolMap(providers, definitionSymbols); - var providerBySymbol = new Dictionary(SymbolEqualityComparer.Default); - foreach (var (provider, symbol) in symbolByProvider) - { - providerBySymbol.TryAdd(symbol, provider); - } - - var queue = new Queue(); - foreach (var rootSymbol in rootSymbols) - { - if (providerBySymbol.TryGetValue(rootSymbol, out var provider)) - { - queue.Enqueue(provider); - } - } - - var visited = new HashSet(); - while (queue.Count > 0) - { - var provider = queue.Dequeue(); - if (!visited.Add(provider)) - { - continue; - } - - var canonicalView = provider.CanonicalView; - if (!symbolByProvider.TryGetValue(canonicalView, out var providerSymbol)) - { - continue; - } - - var referencedSymbols = ProcessPublicApi(canonicalView, providerSymbol, referenceMap, definitionSymbols); - foreach (var referencedSymbol in referencedSymbols) - { - if (providerBySymbol.TryGetValue(referencedSymbol, out var referencedProvider)) - { - queue.Enqueue(referencedProvider); - } - } - - foreach (var (derivedProvider, derivedSymbol) in symbolByProvider) - { - var baseSymbol = derivedProvider.BaseType == null ? null : ResolveType(derivedProvider.BaseType, definitionSymbols); - if (!SymbolEqualityComparer.Default.Equals(baseSymbol, providerSymbol)) - { - continue; - } - - referenceMap.AddInList(providerSymbol, derivedSymbol); - queue.Enqueue(derivedProvider); - } - } - - return referenceMap; - } - private async Task ProcessPublicSymbolAsync(INamedTypeSymbol symbol, ReferenceMap referenceMap, IReadOnlyDictionary> cache) { // only add to reference when myself is public @@ -271,158 +206,6 @@ private async Task AddReferenceToReferenceMapAsync(INamedTypeSymbol symbol, Refe } } - private static IReadOnlyDictionary BuildProviderSymbolMap( - IEnumerable providers, - IReadOnlyList definitions) - { - var result = new Dictionary(); - foreach (var provider in GetProviders(providers)) - { - var canonicalView = provider.CanonicalView; - var symbol = ResolveType(canonicalView.Type, definitions); - if (symbol != null) - { - result[canonicalView] = symbol; - if (!ReferenceEquals(provider, canonicalView)) - { - result[provider] = symbol; - } - } - } - - return result; - } - - private static IEnumerable GetProviders(IEnumerable providers) - { - foreach (var provider in providers) - { - yield return provider; - foreach (var nestedType in GetProviders(provider.CanonicalView.NestedTypes)) - { - yield return nestedType; - } - } - } - - private static IEnumerable ProcessPublicApi( - TypeProvider provider, - INamedTypeSymbol providerSymbol, - ReferenceMap referenceMap, - IReadOnlyList definitions) - { - var referencedSymbols = new HashSet(SymbolEqualityComparer.Default); - AddType(providerSymbol, provider.Type, referenceMap, definitions, referencedSymbols); - AddType(providerSymbol, provider.BaseType, referenceMap, definitions, referencedSymbols); - foreach (var implementedType in provider.Implements) - { - AddType(providerSymbol, implementedType, referenceMap, definitions, referencedSymbols); - } - - foreach (var constructor in provider.Constructors.Where(static c => IsPublicApi(c.Signature.Modifiers))) - { - ProcessMethodSignature(providerSymbol, constructor.Signature, referenceMap, definitions, referencedSymbols); - } - - foreach (var method in provider.Methods.Where(static m => IsPublicApi(m.Signature.Modifiers))) - { - ProcessMethodSignature(providerSymbol, method.Signature, referenceMap, definitions, referencedSymbols); - AddType(providerSymbol, method.Signature.ExplicitInterface, referenceMap, definitions, referencedSymbols); - foreach (var genericArgument in method.Signature.GenericArguments ?? []) - { - AddType(providerSymbol, genericArgument, referenceMap, definitions, referencedSymbols); - } - } - - foreach (var property in provider.Properties.Where(static p => IsPublicApi(p.Modifiers))) - { - AddType(providerSymbol, property.Type, referenceMap, definitions, referencedSymbols); - AddType(providerSymbol, property.ExplicitInterface, referenceMap, definitions, referencedSymbols); - } - - foreach (var field in provider.Fields.Where(static f => IsPublicApi(f.Modifiers))) - { - AddType(providerSymbol, field.Type, referenceMap, definitions, referencedSymbols); - } - - foreach (var nestedType in provider.NestedTypes.Where(static t => IsPublicApi(t.DeclarationModifiers))) - { - AddType(providerSymbol, nestedType.Type, referenceMap, definitions, referencedSymbols); - } - - return referencedSymbols; - } - - private static void ProcessMethodSignature( - INamedTypeSymbol keySymbol, - MethodSignatureBase signature, - ReferenceMap referenceMap, - IReadOnlyList definitions, - HashSet referencedSymbols) - { - AddType(keySymbol, signature.ReturnType, referenceMap, definitions, referencedSymbols); - foreach (var parameter in signature.Parameters) - { - AddType(keySymbol, parameter.Type, referenceMap, definitions, referencedSymbols); - } - } - - private static void AddType( - INamedTypeSymbol keySymbol, - CSharpType? type, - ReferenceMap referenceMap, - IReadOnlyList definitions, - HashSet referencedSymbols) - { - if (type == null) - { - return; - } - - var valueSymbol = ResolveType(type, definitions); - if (valueSymbol != null && referenceMap.AddInList(keySymbol, valueSymbol)) - { - referencedSymbols.Add(valueSymbol); - } - - AddType(keySymbol, type.BaseType, referenceMap, definitions, referencedSymbols); - AddType(keySymbol, type.DeclaringType, referenceMap, definitions, referencedSymbols); - foreach (var argument in type.Arguments) - { - AddType(keySymbol, argument, referenceMap, definitions, referencedSymbols); - } - } - - private static INamedTypeSymbol? ResolveType(CSharpType type, IReadOnlyList definitions) - { - if (type.IsFrameworkType) - { - return null; - } - - foreach (var definition in definitions) - { - if (definition.IsSameType(type)) - { - return definition; - } - } - - return null; - } - - private static bool IsPublicApi(MethodSignatureModifiers modifiers) - => (modifiers.HasFlag(MethodSignatureModifiers.Public) || modifiers.HasFlag(MethodSignatureModifiers.Protected)) - && !modifiers.HasFlag(MethodSignatureModifiers.Private); - - private static bool IsPublicApi(FieldModifiers modifiers) - => (modifiers.HasFlag(FieldModifiers.Public) || modifiers.HasFlag(FieldModifiers.Protected)) - && !modifiers.HasFlag(FieldModifiers.Private); - - private static bool IsPublicApi(TypeSignatureModifiers modifiers) - => (modifiers.HasFlag(TypeSignatureModifiers.Public) || modifiers.HasFlag(TypeSignatureModifiers.Protected)) - && !modifiers.HasFlag(TypeSignatureModifiers.Private); - /// /// This method recursively adds all related types in to the reference map as the value of key /// diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ConfigurationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ConfigurationTests.cs index b0d402825e8..197bb9eb5e8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ConfigurationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ConfigurationTests.cs @@ -156,7 +156,6 @@ public void DisableDocsForProperty() } [Test] - [TestCase("removeOrInternalize")] [TestCase("keepAll")] [TestCase("internalize")] public void UnreferencedTypeHandling(string input) @@ -170,7 +169,6 @@ public void UnreferencedTypeHandling(string input) MockHelpers.LoadMockGenerator(configuration: mockJson); var expected = input switch { - "removeOrInternalize" => Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, "keepAll" => Configuration.UnreferencedTypesHandlingOption.KeepAll, "internalize" => Configuration.UnreferencedTypesHandlingOption.Internalize, _ => throw new ArgumentException("Invalid input", nameof(input)) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs index e478ee98d40..0e574c2f42f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/PostProcessing/PostProcessorTests.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; @@ -294,13 +293,13 @@ public async Task DoesNotRemoveValidAttributes() } [Test] - public async Task InternalizeUsesProviderPublicApiReferences() + public void InternalizeUsesProviderPublicApiReferences() { MockHelpers.LoadMockGenerator(); - var request = new TestTypeProvider("RequestBody", Path.Combine("src", "Generated", "Models", "RequestBody.cs")); - var dependency = new TestTypeProvider("Dependency", Path.Combine("src", "Generated", "Models", "Dependency.cs")); - var response = new TestTypeProvider("ResponseBody", Path.Combine("src", "Generated", "Models", "ResponseBody.cs")); + var request = new TestTypeProvider("RequestBody"); + var dependency = new TestTypeProvider("Dependency"); + var response = new TestTypeProvider("ResponseBody"); response.PropertiesToBuild.Add(new PropertyProvider( null, MethodSignatureModifiers.Public, @@ -309,7 +308,7 @@ public async Task InternalizeUsesProviderPublicApiReferences() new AutoPropertyBody(false), response)); - var client = new TestTypeProvider("SampleClient", Path.Combine("src", "Generated", "SampleClient.cs")); + var client = new TestTypeProvider("SampleClient"); client.FieldsToBuild.Add(new FieldProvider(FieldModifiers.Private, request.Type, "_request", client)); client.MethodsToBuild.Add(new MethodProvider( new MethodSignature("Get", null, MethodSignatureModifiers.Public, response.Type, null, []), @@ -317,52 +316,15 @@ public async Task InternalizeUsesProviderPublicApiReferences() client)); var providers = new[] { client, request, response, dependency }; - var project = CreateProject(providers); var postProcessor = new PostProcessor([]); - var resultProject = await postProcessor.InternalizeAsync(project, providers); + postProcessor.Internalize(providers); - AssertTypeAccessibility(resultProject, "SampleClient", SyntaxKind.PublicKeyword); - AssertTypeAccessibility(resultProject, "ResponseBody", SyntaxKind.PublicKeyword); - AssertTypeAccessibility(resultProject, "Dependency", SyntaxKind.PublicKeyword); - AssertTypeAccessibility(resultProject, "RequestBody", SyntaxKind.InternalKeyword); - } - - private static Project CreateProject(IEnumerable providers) - { - var workspace = new AdhocWorkspace(); - var projectInfo = ProjectInfo.Create( - ProjectId.CreateNewId(), - VersionStamp.Create(), - name: "TestProj", - assemblyName: "TestProj", - language: LanguageNames.CSharp) - .WithMetadataReferences(new[] - { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location) - }); - - var project = workspace.AddProject(projectInfo); - foreach (var provider in providers) - { - var codeFile = new TypeProviderWriter(provider).Write(); - project = project.AddDocument(codeFile.Name, codeFile.Content, ["Generated"]).Project; - } - - return project; - } - - private static void AssertTypeAccessibility(Project project, string typeName, SyntaxKind expectedAccessibility) - { - var type = project.Documents - .Select(document => document.GetSyntaxRootAsync().Result) - .Where(root => root != null) - .SelectMany(root => root!.DescendantNodes().OfType()) - .Single(t => t.Identifier.Text == typeName); - - Assert.IsTrue( - type.Modifiers.Any(expectedAccessibility), - $"Expected {typeName} to have {expectedAccessibility}."); + Assert.IsTrue(client.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); + Assert.IsTrue(response.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); + Assert.IsTrue(dependency.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); + Assert.IsTrue(request.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)); + Assert.IsFalse(request.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); } private class TestPostProcessor : PostProcessor @@ -383,12 +345,10 @@ protected override Task IsRootDocument(Document document) private class TestTypeProvider : TypeProvider { private readonly string _name; - private readonly string _relativeFilePath; - public TestTypeProvider(string name, string relativeFilePath) + public TestTypeProvider(string name = "Test") { _name = name; - _relativeFilePath = relativeFilePath; } public List FieldsToBuild { get; } = []; @@ -399,7 +359,7 @@ public TestTypeProvider(string name, string relativeFilePath) protected override string BuildNamespace() => "Sample"; - protected override string BuildRelativeFilePath() => _relativeFilePath; + protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); protected override TypeSignatureModifiers BuildDeclarationModifiers() => TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/StartUp/GeneratorHandlerTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/StartUp/GeneratorHandlerTests.cs index d8ffdcd323c..eac3a6f1dd8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/StartUp/GeneratorHandlerTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/StartUp/GeneratorHandlerTests.cs @@ -340,7 +340,7 @@ public void AddConfiguredPluginDlls_NoPluginPaths_DoesNothing() new Dictionary(), "TestPackage", false, - Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, + Configuration.UnreferencedTypesHandlingOption.Internalize, null, pluginPaths: null); @@ -358,7 +358,7 @@ public void AddConfiguredPluginDlls_InvalidDirectory_Throws() new Dictionary(), "TestPackage", false, - Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, + Configuration.UnreferencedTypesHandlingOption.Internalize, null, pluginPaths: ["/nonexistent/path"]); @@ -385,7 +385,7 @@ public void AddConfiguredPluginDlls_DirectoryWithPreBuiltDlls_LoadsThem() new Dictionary(), "TestPackage", false, - Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, + Configuration.UnreferencedTypesHandlingOption.Internalize, null, pluginPaths: [testDir]); @@ -422,7 +422,7 @@ namespace AutoBuildPlugin { public class Dummy { } }"); new Dictionary(), "TestPackage", false, - Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, + Configuration.UnreferencedTypesHandlingOption.Internalize, null, pluginPaths: [testDir]); @@ -464,7 +464,7 @@ namespace Plugin2 { public class Dummy { } }"); new Dictionary(), "TestPackage", false, - Configuration.UnreferencedTypesHandlingOption.RemoveOrInternalize, + Configuration.UnreferencedTypesHandlingOption.Internalize, null, pluginPaths: [testDir1, testDir2]); diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 6dcfdea3555..ef647e8f07c 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -78,9 +78,9 @@ Set to `false` to skip generation of convenience methods. The default value is ` ### `unreferenced-types-handling` -**Type:** `"removeOrInternalize" | "internalize" | "keepAll"` +**Type:** `"internalize" | "keepAll"` -Defines the strategy on how to handle unreferenced types. The default value is `removeOrInternalize`. +Defines the strategy on how to handle unreferenced types. The default value is `internalize`. ### `new-project` From ee4dcc3d5480d61d4cccbb62bd217da1d93428b1 Mon Sep 17 00:00:00 2001 From: Dapeng Zhang Date: Thu, 25 Jun 2026 19:43:42 +0800 Subject: [PATCH 3/3] fix(csharp): skip file model providers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/ScmTypeFactory.cs | 3 +- .../test/ScmTypeFactoryTests.cs | 13 ++ .../src/PostProcessing/PostProcessor.cs | 2 +- .../ProviderReferenceMapBuilder.cs | 11 +- .../Generated/Internal/BinaryContentHelper.cs | 175 ++++++++++++++++++ .../PipelineRequestHeadersExtensions.cs | 48 +++++ .../Internal/Utf8JsonBinaryContent.cs | 61 ++++++ ...kenHeaderResponseResponse.Serialization.cs | 1 - ...ContinuationTokenHeaderResponseResponse.cs | 1 - ...ContinuationTokenResponse.Serialization.cs | 1 - .../ListWithContinuationTokenResponse.cs | 1 - .../ListWithNextLinkResponse.Serialization.cs | 1 - .../Models/ListWithNextLinkResponse.cs | 1 - ...ithStringNextLinkResponse.Serialization.cs | 1 - .../Models/ListWithStringNextLinkResponse.cs | 1 - .../Models/PageThing.Serialization.cs | 1 - .../src/Generated/Models/PageThing.cs | 1 - 17 files changed, 305 insertions(+), 18 deletions(-) create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs index 15596c96918..13ecacc98f1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs @@ -269,7 +269,8 @@ public virtual MethodBodyStatement SerializeXmlValue( SerializationFormat format) => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, format); - protected override ModelProvider? CreateModelCore(InputModelType model) => new ScmModelProvider(model); + protected override ModelProvider? CreateModelCore(InputModelType model) + => model.IsFileType ? null : new ScmModelProvider(model); protected override ModelFactoryProvider CreateModelFactoryCore(IEnumerable models) => new ScmModelFactoryProvider(models); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmTypeFactoryTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmTypeFactoryTests.cs index 98f1b9a5fd0..c7fc0dc70cf 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmTypeFactoryTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmTypeFactoryTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; @@ -146,6 +147,18 @@ public void TestCreateSerializations_ReturnsBothMrwAndMultipart_WhenJsonAndMpfdU "Expected a multipart serialization provider for a model with MultipartFormData usage."); } + [Test] + public void FileTypeDoesNotCreateModelProvider() + { + var file = InputFactory.Model("File", @namespace: "TypeSpec.Http"); + typeof(InputModelType).GetProperty(nameof(InputModelType.IsFileType))!.SetValue(file, true); + + MockHelpers.LoadMockGenerator(inputModels: () => [file]); + + var provider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(file); + Assert.IsNull(provider); + } + // ScmTypeFactory overrides CreateModelCore to return ScmModelProvider. External-type // handling lives in the (non-overridable) base TypeFactory.CreateModel, so it must still // apply here. This guards against regressing the fix by re-introducing external handling diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs index bdf7913eeea..20505bed0a3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/PostProcessor.cs @@ -138,7 +138,7 @@ public void Internalize(IReadOnlyList typeProviders) } private bool IsRootProvider(TypeProvider provider) - => IsClientProvider(provider) || ShouldKeepProvider(provider, _typesToKeep); + => IsClientProvider(provider) || provider.CustomCodeView != null || ShouldKeepProvider(provider, _typesToKeep); private bool IsExcludedProvider(TypeProvider provider) => IsModelFactoryProvider(provider) || ShouldKeepProvider(provider, _additionalNonRootTypeNames); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ProviderReferenceMapBuilder.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ProviderReferenceMapBuilder.cs index b3d9172c3f2..68e26bd167c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ProviderReferenceMapBuilder.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/PostProcessing/ProviderReferenceMapBuilder.cs @@ -122,8 +122,7 @@ private void AddType(CSharpType? type, HashSet referencedTypes) return; } - var provider = ResolveType(type); - if (provider != null) + foreach (var provider in ResolveTypes(type)) { referencedTypes.Add(provider); } @@ -144,19 +143,19 @@ private void AddType(CSharpType? type, HashSet referencedTypes) } } - private TypeProvider? ResolveType(CSharpType type) + private IEnumerable ResolveTypes(CSharpType type) { if (type.IsFrameworkType) { - return null; + return []; } if (CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && provider != null) { - return provider; + return _providers.Where(candidate => ReferenceEquals(candidate, provider) || candidate.Type.AreNamesEqual(type)); } - return _providers.FirstOrDefault(provider => + return _providers.Where(provider => provider.Type.AreNamesEqual(type) || provider.CanonicalView.Type.AreNamesEqual(type)); } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs new file mode 100644 index 00000000000..b76cf24aa37 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/BinaryContentHelper.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Xml; + +namespace SampleTypeSpec +{ + internal static partial class BinaryContentHelper + { + /// + public static BinaryContent FromEnumerable(IEnumerable enumerable) + where T : notnull + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteStartArray(); + foreach (var item in enumerable) + { + content.JsonWriter.WriteObjectValue(item, ModelSerializationExtensions.WireOptions); + } + content.JsonWriter.WriteEndArray(); + + return content; + } + + /// + public static BinaryContent FromEnumerable(IEnumerable enumerable) + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteStartArray(); + foreach (var item in enumerable) + { + if (item == null) + { + content.JsonWriter.WriteNullValue(); + } + else + { +#if NET6_0_OR_GREATER + content.JsonWriter.WriteRawValue(item); +#else + using (JsonDocument document = JsonDocument.Parse(item)) + { + JsonSerializer.Serialize(content.JsonWriter, document.RootElement); + } +#endif + } + } + content.JsonWriter.WriteEndArray(); + + return content; + } + + /// + public static BinaryContent FromEnumerable(ReadOnlySpan span) + where T : notnull + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteStartArray(); + int i = 0; + for (; i < span.Length; i++) + { + content.JsonWriter.WriteObjectValue(span[i], ModelSerializationExtensions.WireOptions); + } + content.JsonWriter.WriteEndArray(); + + return content; + } + + /// + public static BinaryContent FromDictionary(IDictionary dictionary) + where TValue : notnull + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteStartObject(); + foreach (var item in dictionary) + { + content.JsonWriter.WritePropertyName(item.Key); + content.JsonWriter.WriteObjectValue(item.Value, ModelSerializationExtensions.WireOptions); + } + content.JsonWriter.WriteEndObject(); + + return content; + } + + /// + public static BinaryContent FromDictionary(IDictionary dictionary) + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteStartObject(); + foreach (var item in dictionary) + { + content.JsonWriter.WritePropertyName(item.Key); + if (item.Value == null) + { + content.JsonWriter.WriteNullValue(); + } + else + { +#if NET6_0_OR_GREATER + content.JsonWriter.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(content.JsonWriter, document.RootElement); + } +#endif + } + } + content.JsonWriter.WriteEndObject(); + + return content; + } + + /// + public static BinaryContent FromObject(object value) + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); + content.JsonWriter.WriteObjectValue(value, ModelSerializationExtensions.WireOptions); + return content; + } + + /// + public static BinaryContent FromObject(BinaryData value) + { + Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); +#if NET6_0_OR_GREATER + content.JsonWriter.WriteRawValue(value); +#else + using (JsonDocument document = JsonDocument.Parse(value)) + { + JsonSerializer.Serialize(content.JsonWriter, document.RootElement); + } +#endif + return content; + } + + /// + /// + /// + public static BinaryContent FromEnumerable(IEnumerable enumerable, string rootNameHint, string childNameHint) + where T : notnull + { + using (MemoryStream stream = new MemoryStream(256)) + { + using (XmlWriter writer = XmlWriter.Create(stream, ModelSerializationExtensions.XmlWriterSettings)) + { + writer.WriteStartElement(rootNameHint); + foreach (var item in enumerable) + { + writer.WriteObjectValue(item, ModelSerializationExtensions.WireOptions, childNameHint); + } + writer.WriteEndElement(); + } + + if (stream.Position > int.MaxValue) + { + return BinaryContent.Create(BinaryData.FromStream(stream)); + } + else + { + return BinaryContent.Create(new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position))); + } + } + } + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs new file mode 100644 index 00000000000..69ddd4aee41 --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/PipelineRequestHeadersExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Linq; + +namespace SampleTypeSpec +{ + internal static partial class PipelineRequestHeadersExtensions + { + /// + /// The name. + /// The value. + /// The delimiter. + public static void SetDelimited(this PipelineRequestHeaders headers, string name, IEnumerable value, string delimiter) + { + IEnumerable stringValues = value.Select(v => TypeFormatters.ConvertToString(v)); + headers.Set(name, string.Join(delimiter, stringValues)); + } + + /// + /// The name. + /// The value. + /// The delimiter. + /// The format. + public static void SetDelimited(this PipelineRequestHeaders headers, string name, IEnumerable value, string delimiter, SerializationFormat format) + { + IEnumerable stringValues = value.Select(v => TypeFormatters.ConvertToString(v, format)); + headers.Set(name, string.Join(delimiter, stringValues)); + } + + /// + /// The prefix to prepend to each header key. + /// The dictionary of headers to add. + public static void Add(this PipelineRequestHeaders headers, string prefix, IDictionary value) + { + foreach (var header in value) + { + headers.Add(prefix + header.Key, header.Value); + } + } + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs new file mode 100644 index 00000000000..f4586e305fc --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/Utf8JsonBinaryContent.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace SampleTypeSpec +{ + internal partial class Utf8JsonBinaryContent : BinaryContent + { + private readonly MemoryStream _stream; + private readonly BinaryContent _content; + + public Utf8JsonBinaryContent() + { + _stream = new MemoryStream(); + _content = Create(_stream); + JsonWriter = new Utf8JsonWriter(_stream); + } + + /// Gets the JsonWriter. + public Utf8JsonWriter JsonWriter { get; } + + /// The stream containing the data to be written. + /// The cancellation token to use. + public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default) + { + await JsonWriter.FlushAsync().ConfigureAwait(false); + await _content.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); + } + + /// The stream containing the data to be written. + /// The cancellation token to use. + public override void WriteTo(Stream stream, CancellationToken cancellationToken = default) + { + JsonWriter.Flush(); + _content.WriteTo(stream, cancellationToken); + } + + /// + public override bool TryComputeLength(out long length) + { + length = JsonWriter.BytesCommitted + JsonWriter.BytesPending; + return true; + } + + public override void Dispose() + { + JsonWriter.Dispose(); + _content.Dispose(); + _stream.Dispose(); + } + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs index 2ebf7fbba85..87363728c56 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs @@ -13,7 +13,6 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenHeaderResponseResponse. internal partial class ListWithContinuationTokenHeaderResponseResponse : IJsonModel { /// Initializes a new instance of for deserialization. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs index dc281b42e3d..fe874d2c945 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs @@ -11,7 +11,6 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenHeaderResponseResponse. internal partial class ListWithContinuationTokenHeaderResponseResponse { /// Keeps track of any properties unknown to the library. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs index 6e622e685fe..66f3b44350c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs @@ -13,7 +13,6 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenResponse. internal partial class ListWithContinuationTokenResponse : IJsonModel { /// Initializes a new instance of for deserialization. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs index e689ab9f9bd..e5b9b8c3d17 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs @@ -11,7 +11,6 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenResponse. internal partial class ListWithContinuationTokenResponse { /// Keeps track of any properties unknown to the library. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs index 96bd3d2b4cb..64d4e703b80 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs @@ -13,7 +13,6 @@ namespace SampleTypeSpec { - /// The ListWithNextLinkResponse. internal partial class ListWithNextLinkResponse : IJsonModel { /// Initializes a new instance of for deserialization. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs index 96de907bd81..801674109f8 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs @@ -11,7 +11,6 @@ namespace SampleTypeSpec { - /// The ListWithNextLinkResponse. internal partial class ListWithNextLinkResponse { /// Keeps track of any properties unknown to the library. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs index 8c69549a2dd..48f530aa5d3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs @@ -13,7 +13,6 @@ namespace SampleTypeSpec { - /// The ListWithStringNextLinkResponse. internal partial class ListWithStringNextLinkResponse : IJsonModel { /// Initializes a new instance of for deserialization. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs index 38c936b133d..8a3b70315db 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs @@ -11,7 +11,6 @@ namespace SampleTypeSpec { - /// The ListWithStringNextLinkResponse. internal partial class ListWithStringNextLinkResponse { /// Keeps track of any properties unknown to the library. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.Serialization.cs index cb23fca29f1..df7d8ba90e0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.Serialization.cs @@ -13,7 +13,6 @@ namespace SampleTypeSpec { - /// The PageThing. internal partial class PageThing : IJsonModel { /// Initializes a new instance of for deserialization. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.cs index 0b9328af0fc..417a8f4d46d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/PageThing.cs @@ -11,7 +11,6 @@ namespace SampleTypeSpec { - /// The PageThing. internal partial class PageThing { /// Keeps track of any properties unknown to the library.