From f0a53ce25cc0870e1bf460acde230f462ce4ad4f Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Tue, 24 Mar 2026 12:56:31 -0700 Subject: [PATCH] feat: Add CodeGenNamespace assembly attribute for enum namespace customization Add a new [assembly: CodeGenNamespace] attribute that allows changing the namespace of generated types (especially fixed enums) without redefining all members in custom code. Usage: [assembly: CodeGenNamespace("OriginalEnum", "NewNamespace.Models")] This complements the existing @clientNamespace TCGC decorator for scenarios where users cannot modify the TypeSpec spec or need C#-specific overrides. Fixes #9086 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/CodeModelGenerator.cs | 25 +++- .../CodeGenNamespaceAttributeDefinition.cs | 79 ++++++++++++ .../src/Providers/TypeProvider.cs | 24 +++- .../src/SourceInput/CodeGenAttributes.cs | 2 + .../src/SourceInput/SourceInputModel.cs | 32 +++++ .../test/CustomizationAttributeTests.cs | 8 ++ .../EnumProviderCustomizationTests.cs | 114 ++++++++++++++++++ .../NamespaceOverride.cs | 3 + .../NamespaceOverride.cs | 3 + .../NamespaceOverride.cs | 3 + .../MockInputEnum.cs | 16 +++ .../NamespaceOverride.cs | 4 + .../ModelProviders/ModelCustomizationTests.cs | 18 +++ .../NamespaceOverride.cs | 3 + .../Internal/CodeGenNamespaceAttribute.cs | 29 +++++ 15 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs index 6fc87d7ced9..2b18952b4e8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs @@ -90,6 +90,28 @@ internal set } } + /// + /// Safely gets a namespace override from the SourceInputModel if it's available. + /// Returns null if the generator or SourceInputModel is not yet initialized. + /// + internal static string? GetNamespaceOverride(string typeName) + { + if (_instance == null) + { + return null; + } + + try + { + return _instance.SourceInputModel.GetNamespaceOverride(typeName); + } + catch (InvalidOperationException) + { + // SourceInputModel not yet initialized + return null; + } + } + public string LicenseHeader => Configuration.LicenseInfo?.Header ?? string.Empty; public virtual OutputLibrary OutputLibrary { get; } = new(); public virtual InputLibrary InputLibrary => _inputLibrary; @@ -103,7 +125,8 @@ internal set new CodeGenTypeAttributeDefinition(), new CodeGenMemberAttributeDefinition(), new CodeGenSuppressAttributeDefinition(), - new CodeGenSerializationAttributeDefinition() + new CodeGenSerializationAttributeDefinition(), + new CodeGenNamespaceAttributeDefinition() ]; protected internal virtual void Configure() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs new file mode 100644 index 00000000000..1de8466743a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Statements; +using static Microsoft.TypeSpec.Generator.Snippets.Snippet; + +namespace Microsoft.TypeSpec.Generator.Providers +{ + internal class CodeGenNamespaceAttributeDefinition : TypeProvider + { + protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", "Internal", $"{Name}.cs"); + + protected override string BuildName() => "CodeGenNamespaceAttribute"; + + protected override string BuildNamespace() => CodeModelGenerator.CustomizationAttributeNamespace; + + private protected sealed override NamedTypeSymbolProvider? BuildCustomCodeView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null; + private protected sealed override NamedTypeSymbolProvider? BuildLastContractView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null; + + protected override TypeSignatureModifiers BuildDeclarationModifiers() => + TypeSignatureModifiers.Internal | TypeSignatureModifiers.Class; + + protected internal override CSharpType[] BuildImplements() => [typeof(Attribute)]; + + protected override IReadOnlyList BuildAttributes() + { + return [new AttributeStatement(typeof(AttributeUsageAttribute), + [FrameworkEnumValue(AttributeTargets.Assembly)], + [ + new KeyValuePair("AllowMultiple", True) + ])]; + } + + protected internal override PropertyProvider[] BuildProperties() => + [ + new PropertyProvider( + $"Gets the original name of the type whose namespace should be changed.", + MethodSignatureModifiers.Public, + typeof(string), + "TypeName", + new AutoPropertyBody(false), + this), + new PropertyProvider( + $"Gets the new namespace for the type.", + MethodSignatureModifiers.Public, + typeof(string), + "Namespace", + new AutoPropertyBody(false), + this) + ]; + + protected internal override ConstructorProvider[] BuildConstructors() + { + var typeNameParameter = new ParameterProvider("typeName", $"The original name of the type.", typeof(string)); + var namespaceParameter = new ParameterProvider("namespace", $"The new namespace for the type.", typeof(string)); + + return + [ + new ConstructorProvider( + new ConstructorSignature( + Type, + null, + MethodSignatureModifiers.Public, + [typeNameParameter, namespaceParameter]), + new[] + { + This.Property("TypeName").Assign(typeNameParameter).Terminate(), + This.Property("Namespace").Assign(namespaceParameter).Terminate() + }, + this) + ]; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index e34c6b33e35..f99a06540ee 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -49,7 +49,7 @@ protected TypeProvider() : this(null) private protected virtual TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) => CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract( - generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(), + generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? CodeModelGenerator.GetNamespaceOverride(BuildName()) ?? BuildNamespace(), generatedTypeName ?? CustomCodeView?.Name ?? BuildName(), DeclaringTypeProvider?.Type.Name); @@ -134,7 +134,7 @@ public string? Deprecated public CSharpType Type => _type ??= new( CustomCodeView?.Name ?? BuildName(), - CustomCodeView?.Type.Namespace ?? BuildNamespace(), + GetResolvedNamespace(), this is EnumProvider || DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct) || DeclarationModifiers.HasFlag(TypeSignatureModifiers.Enum), @@ -149,6 +149,26 @@ this is EnumProvider || protected virtual bool GetIsEnum() => false; public bool IsEnum => GetIsEnum(); + /// + /// Resolves the namespace for this type, checking custom code view first, + /// then assembly-level CodeGenNamespace overrides, then the default namespace. + /// + private string GetResolvedNamespace() + { + if (CustomCodeView?.Type.Namespace is { } customNs) + { + return customNs; + } + + var overrideNs = CodeModelGenerator.GetNamespaceOverride(BuildName()); + if (overrideNs != null) + { + return overrideNs; + } + + return BuildNamespace(); + } + protected virtual string BuildNamespace() => CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace; private TypeSignatureModifiers? _declarationModifiers; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs index 973d262735f..26a4dae697b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs @@ -19,6 +19,8 @@ public static class CodeGenAttributes public const string CodeGenSerializationAttributeName = "CodeGenSerializationAttribute"; + public const string CodeGenNamespaceAttributeName = "CodeGenNamespaceAttribute"; + private const string SerializationName = "SerializationName"; private const string SerializationValueHook = "SerializationValueHook"; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs index 54c17f609ba..9180dc8fa93 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs @@ -15,6 +15,7 @@ public class SourceInputModel public Compilation? LastContract { get; } private readonly Lazy> _nameMap; + private readonly Lazy> _namespaceOverrides; public SourceInputModel(Compilation? customization, Compilation? lastContract) { @@ -22,6 +23,7 @@ public SourceInputModel(Compilation? customization, Compilation? lastContract) LastContract = lastContract; _nameMap = new(PopulateNameMap); + _namespaceOverrides = new(PopulateNamespaceOverrides); } private IReadOnlyDictionary PopulateNameMap() @@ -52,6 +54,14 @@ private IReadOnlyDictionary PopulateNameMap() return FindTypeInCustomization(Customization, ns, name, false, declaringTypeName); } + /// + /// Gets the namespace override for a type if one was specified via [assembly: CodeGenNamespace] in custom code. + /// + public string? GetNamespaceOverride(string typeName) + { + return _namespaceOverrides.Value.TryGetValue(typeName, out var ns) ? ns : null; + } + public TypeProvider? FindForTypeInLastContract(string ns, string name, string? declaringTypeName = null) { return FindTypeInCompilation(LastContract, ns, name, true, declaringTypeName, includeInternal: false); @@ -113,6 +123,28 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri return type != null ? new NamedTypeSymbolProvider(type, compilation) : null; } + private IReadOnlyDictionary PopulateNamespaceOverrides() + { + var overrides = new Dictionary(); + if (Customization == null) + { + return overrides; + } + + foreach (var attribute in Customization.Assembly.GetAttributes()) + { + if (attribute.AttributeClass?.Name == CodeGenAttributes.CodeGenNamespaceAttributeName + && attribute.ConstructorArguments.Length >= 2 + && attribute.ConstructorArguments[0].Value is string typeName + && attribute.ConstructorArguments[1].Value is string @namespace) + { + overrides[typeName] = @namespace; + } + } + + return overrides; + } + private bool TryGetName(ISymbol symbol, [NotNullWhen(true)] out string? name) { name = null; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs index 4f107ab227d..4233063d6e0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs @@ -45,5 +45,13 @@ public void CodeGenTypeAttributeEmitted() var codeGenTypeAttribute = new CodeGenTypeAttribute("PropertyName"); Assert.AreEqual("PropertyName", codeGenTypeAttribute.OriginalName); } + + [Test] + public void CodeGenNamespaceAttributeEmitted() + { + var codeGenNamespaceAttribute = new CodeGenNamespaceAttribute("EnumTypeName", "New.Namespace.Models"); + Assert.AreEqual("EnumTypeName", codeGenNamespaceAttribute.TypeName); + Assert.AreEqual("New.Namespace.Models", codeGenNamespaceAttribute.Namespace); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs index b03e2ab0457..1d2567146a4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Linq; using System.Threading.Tasks; +using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Tests.Common; @@ -29,5 +31,117 @@ public async Task CanCustomizeExtensibleEnumAccessibility() Assert.IsTrue(enumType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); Assert.IsFalse(enumType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)); } + + [Test] + public async Task CanChangeFixedEnumNamespace() + { + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var input = InputFactory.Int32Enum("mockInputEnum", + [ + ("One", 1), + ("Two", 2) + ], + isExtensible: false); + + var enumType = EnumProvider.Create(input); + Assert.IsNotNull(enumType); + Assert.IsNull(enumType.CustomCodeView, "CustomCodeView should be null since no custom type is defined"); + Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace); + Assert.AreEqual("MockInputEnum", enumType.Type.Name); + Assert.IsTrue(enumType is FixedEnumProvider, "Enum should remain a FixedEnumProvider"); + Assert.AreEqual(2, enumType.EnumValues.Count); + Assert.AreEqual(2, enumType.Fields.Count); + } + + [Test] + public async Task CanChangeExtensibleEnumNamespace() + { + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var input = InputFactory.Int32Enum("mockInputEnum", + [ + ("One", 1), + ("Two", 2) + ], + isExtensible: true); + + var enumType = EnumProvider.Create(input); + Assert.IsNotNull(enumType); + Assert.IsNull(enumType.CustomCodeView, "CustomCodeView should be null since no custom type is defined"); + Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace); + Assert.AreEqual("MockInputEnum", enumType.Type.Name); + Assert.IsTrue(enumType is ExtensibleEnumProvider, "Enum should remain an ExtensibleEnumProvider"); + Assert.AreEqual(2, enumType.EnumValues.Count); + } + + [Test] + public async Task CanChangeFixedEnumNamespacePreservesMembers() + { + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var input = InputFactory.Int32Enum("mockInputEnum", + [ + ("Red", 1), + ("Green", 2), + ("Blue", 3) + ], + isExtensible: false); + + var enumType = EnumProvider.Create(input); + Assert.IsNotNull(enumType); + Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace); + + // Verify all enum members are preserved + Assert.AreEqual(3, enumType.EnumValues.Count); + Assert.AreEqual("Red", enumType.EnumValues[0].Name); + Assert.AreEqual("Green", enumType.EnumValues[1].Name); + Assert.AreEqual("Blue", enumType.EnumValues[2].Name); + // Verify fields match enum values + Assert.AreEqual(3, enumType.Fields.Count); + } + + [Test] + public async Task CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace() + { + // When both [CodeGenType] and [assembly: CodeGenNamespace] target the same type, + // CodeGenType (CustomCodeView) takes precedence. + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var input = InputFactory.Int32Enum("mockInputEnum", + [ + ("One", 1), + ("Two", 2) + ], + isExtensible: false); + + var enumType = EnumProvider.Create(input); + Assert.IsNotNull(enumType); + Assert.IsNotNull(enumType.CustomCodeView, "CustomCodeView should exist from [CodeGenType]"); + // CodeGenType's namespace wins over CodeGenNamespace + Assert.AreEqual("CustomCodeView.Models", enumType.Type.Namespace); + Assert.AreEqual("RenamedEnum", enumType.Type.Name); + } + + [Test] + public async Task CodeGenNamespaceIgnoredForNonMatchingType() + { + // [assembly: CodeGenNamespace("NonExistentType", ...)] should have no effect on other types. + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var input = InputFactory.Int32Enum("mockInputEnum", + [ + ("One", 1), + ("Two", 2) + ], + isExtensible: false); + + var enumType = EnumProvider.Create(input); + Assert.IsNotNull(enumType); + Assert.IsNull(enumType.CustomCodeView); + // Should use the default namespace, not the assembly attribute's namespace + Assert.AreEqual("Sample.Models", enumType.Type.Namespace); + Assert.AreEqual("MockInputEnum", enumType.Type.Name); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs new file mode 100644 index 00000000000..d28811202eb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs @@ -0,0 +1,3 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs new file mode 100644 index 00000000000..d28811202eb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs @@ -0,0 +1,3 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs new file mode 100644 index 00000000000..d28811202eb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs @@ -0,0 +1,3 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs new file mode 100644 index 00000000000..e6e3400e260 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs @@ -0,0 +1,16 @@ +#nullable disable + +using Sample; +using SampleTypeSpec; +using Microsoft.TypeSpec.Generator.Customizations; + +// CodeGenType takes precedence over CodeGenNamespace when both are present +[assembly: CodeGenNamespace("MockInputEnum", "OverriddenNamespace.Models")] + +namespace CustomCodeView.Models +{ + [CodeGenType("MockInputEnum")] + public enum RenamedEnum + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs new file mode 100644 index 00000000000..86981dbb78c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs @@ -0,0 +1,4 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +// CodeGenNamespace targets a type that does not exist in the input +[assembly: CodeGenNamespace("NonExistentType", "SomeOther.Namespace")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index 556948c476e..7e44a96fa16 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -45,6 +45,24 @@ public async Task CanChangeEnumName() AssertCommon(enumProvider, "NewNamespace.Models", "CustomizedEnum"); } + [Test] + public async Task CanChangeModelNamespaceWithAssemblyAttribute() + { + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var props = new[] + { + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + var modelTypeProvider = new ModelProvider(inputModel); + + Assert.IsNull(modelTypeProvider.CustomCodeView, "CustomCodeView should be null since no custom type is defined"); + Assert.AreEqual("NewNamespace.Models", modelTypeProvider.Type.Namespace); + Assert.AreEqual("MockInputModel", modelTypeProvider.Type.Name); + } + [Test] public async Task CanChangePropertyName() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs new file mode 100644 index 00000000000..b472d0615da --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs @@ -0,0 +1,3 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +[assembly: CodeGenNamespace("MockInputModel", "NewNamespace.Models")] diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs new file mode 100644 index 00000000000..086a9d2a78b --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; + +namespace Microsoft.TypeSpec.Generator.Customizations +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal partial class CodeGenNamespaceAttribute : Attribute + { + /// The original name of the type. + /// The new namespace for the type. + public CodeGenNamespaceAttribute(string typeName, string @namespace) + { + TypeName = typeName; + Namespace = @namespace; + } + + /// Gets the original name of the type whose namespace should be changed. + public string TypeName { get; } + + /// Gets the new namespace for the type. + public string Namespace { get; } + } +}