diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs index cb411d1d53f..fc218d46db1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/FieldProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input.Extensions; using Microsoft.TypeSpec.Generator.Primitives; @@ -147,8 +148,9 @@ public void Update( [MemberNotNull(nameof(_parameter))] private void InitializeParameter() { + var paramAttributes = Attributes.Where(a => a.Type.Namespace != CodeModelGenerator.CustomizationAttributeNamespace).ToArray(); _parameter = new(() => new ParameterProvider( - Name.ToVariableName(), Description ?? FormattableStringHelpers.Empty, Type, field: this, wireInfo: WireInfo, attributes: Attributes)); + Name.ToVariableName(), Description ?? FormattableStringHelpers.Empty, Type, field: this, wireInfo: WireInfo, attributes: paramAttributes)); } private MemberExpression? _asMember; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs index eee24478749..699f97c788c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs @@ -122,7 +122,8 @@ protected internal override FieldProvider[] BuildFields() fieldSymbol.Name, this, GetSymbolXmlDoc(fieldSymbol, "summary"), - initializationValue: GetFieldInitializer(fieldSymbol)) + initializationValue: GetFieldInitializer(fieldSymbol), + attributes: fieldSymbol.GetAttributes().Select(a => new AttributeStatement(a)).ToArray()) { OriginalName = GetOriginalName(fieldSymbol) }; @@ -146,7 +147,8 @@ protected internal override PropertyProvider[] BuildProperties() new AutoPropertyBody( propertySymbol.SetMethod is not null, InitializationExpression: GetPropertyInitializer(propertySymbol)), - this) + this, + attributes: propertySymbol.GetAttributes().Select(a => new AttributeStatement(a)).ToArray()) { OriginalName = GetOriginalName(propertySymbol), CustomProvider = new(() => propertySymbol.Type is INamedTypeSymbol propertyNamedTypeSymbol 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..7a98d5fbf4a 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 @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -1698,6 +1699,50 @@ public async Task CanCustomizeBaseModelToSystemType() "System.Exception is from a referenced assembly and should use SystemObjectTypeProvider"); } + [Test] + public async Task CanReadPropertyAttributes() + { + await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var props = new[] + { + InputFactory.Property("Prop1", InputPrimitiveType.String) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + var modelTypeProvider = new ModelProvider(inputModel); + var customCodeView = modelTypeProvider.CustomCodeView; + + Assert.IsNotNull(customCodeView); + Assert.AreEqual(1, customCodeView!.Properties.Count); + + var customProperty = customCodeView.Properties[0]; + Assert.AreEqual("Prop1", customProperty.Name); + + // Verify that attributes from custom code are populated, including a custom non-system attribute + Assert.AreEqual(3, customProperty.Attributes.Count); + + // Validate [Obsolete("This property is now deprecated.", DiagnosticId = "OBS001")] - type, arguments, and positional arguments + var obsoleteAttr = customProperty.Attributes.Single(a => new CSharpType(typeof(ObsoleteAttribute)).Equals(a.Type)); + Assert.AreEqual(1, obsoleteAttr.Arguments.Count); + Assert.AreEqual(1, obsoleteAttr.PositionalArguments.Count); + Assert.AreEqual("DiagnosticId", obsoleteAttr.PositionalArguments[0].Key); + + // Validate [EditorBrowsable(EditorBrowsableState.Never)] - type and arguments + var editorBrowsableAttr = customProperty.Attributes.Single(a => new CSharpType(typeof(System.ComponentModel.EditorBrowsableAttribute)).Equals(a.Type)); + Assert.AreEqual(1, editorBrowsableAttr.Arguments.Count); + + // Validate [Custom("custom message")] - custom non-system attribute does not throw + var customAttr = customProperty.Attributes.Single(a => a.Type.Name == "CustomAttribute"); + Assert.AreEqual(1, customAttr.Arguments.Count); + + // Verify that field attributes from custom code are populated + var customField = customCodeView.Fields.Single(f => f.Name == "_customField"); + Assert.AreEqual(1, customField.Attributes.Count); + var fieldObsoleteAttr = customField.Attributes.Single(a => new CSharpType(typeof(ObsoleteAttribute)).Equals(a.Type)); + Assert.AreEqual(1, fieldObsoleteAttr.Arguments.Count); + } + private class TestNameVisitor : NameVisitor { public TypeProvider? InvokeVisit(TypeProvider type) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanReadPropertyAttributes/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanReadPropertyAttributes/MockInputModel.cs new file mode 100644 index 00000000000..83e1984f82c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanReadPropertyAttributes/MockInputModel.cs @@ -0,0 +1,23 @@ +#nullable disable + +using System; +using System.ComponentModel; + +namespace Sample.Models +{ + public class CustomAttribute : Attribute + { + public CustomAttribute(string message) { } + } + + public partial class MockInputModel + { + [Obsolete("This property is now deprecated.", DiagnosticId = "OBS001")] + [EditorBrowsable(EditorBrowsableState.Never)] + [Custom("custom message")] + public string Prop1 { get; set; } + + [Obsolete("This field is now deprecated.")] + private int _customField; + } +}