From 42dd88a3cc8cd3e62f75a586c64da60bb7b7517d Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Thu, 13 Aug 2026 16:01:20 -0500 Subject: [PATCH 01/22] add acaornym naming for dates --- .../XmlAdvancedModelXmlTests.cs | 4 +- ...onHandlesNullableDateTimeOffsetProperty.cs | 4 +- .../XmlDeserializationTests.cs | 2 +- .../XmlSerializationTests.cs | 2 +- .../src/Providers/ParameterProvider.cs | 4 +- .../src/Providers/PropertyProvider.cs | 4 +- .../src/Utilities/CSharpNameExtensions.cs | 89 +++++++++++++++++++ .../test/Providers/ParameterProviderTests.cs | 43 +++++++++ .../test/Providers/PropertyProviderTests.cs | 88 ++++++++++++++++++ .../Models/XmlAdvancedModel.Serialization.cs | 8 +- .../src/Generated/Models/XmlAdvancedModel.cs | 14 +-- .../Generated/SampleTypeSpecModelFactory.cs | 6 +- 12 files changed, 246 insertions(+), 22 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/XmlAdvancedModelXmlTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/XmlAdvancedModelXmlTests.cs index f5cb1a54a62..7515ce441c1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/XmlAdvancedModelXmlTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/XmlAdvancedModelXmlTests.cs @@ -55,7 +55,7 @@ protected override void CompareModels(XmlAdvancedModel model, XmlAdvancedModel m Assert.AreEqual(model.Metadata.Count, model2.Metadata.Count); // Compare date/time and duration - Assert.AreEqual(model.CreatedAt, model2.CreatedAt); + Assert.AreEqual(model.CreatedOn, model2.CreatedOn); Assert.AreEqual(model.Duration, model2.Duration); // Compare enums @@ -102,7 +102,7 @@ protected override void VerifyModel(XmlAdvancedModel model, string format) Assert.AreEqual("value2", model.Metadata["key2"]); // Verify date/time - Assert.AreEqual(new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.Zero), model.CreatedAt); + Assert.AreEqual(new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.Zero), model.CreatedOn); Assert.AreEqual(new TimeSpan(1, 30, 0), model.Duration); // Verify enums diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/XmlSerializationHandlesNullableDateTimeOffsetProperty.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/XmlSerializationHandlesNullableDateTimeOffsetProperty.cs index a64a6102cfb..a2e945aa23e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/XmlSerializationHandlesNullableDateTimeOffsetProperty.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/XmlSerializationHandlesNullableDateTimeOffsetProperty.cs @@ -63,10 +63,10 @@ internal virtual void XmlModelWriteCore(global::System.Xml.XmlWriter writer, glo throw new global::System.FormatException($"The model {nameof(global::Sample.Models.TestXmlModel)} does not support writing '{format}' format."); } - if (global::Sample.Optional.IsDefined(Timestamp)) + if (global::Sample.Optional.IsDefined(On)) { writer.WriteStartElement("timestamp"); - writer.WriteStringValue(Timestamp.Value, "O"); + writer.WriteStringValue(On.Value, "O"); writer.WriteEndElement(); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index c34ae949f14..52666216903 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -261,7 +261,7 @@ public void XmlDeserializationHandlesDateTimeOffsetProperty() Assert.IsNotNull(xmlDeserializationMethod); var methodBody = xmlDeserializationMethod!.BodyStatements!.ToDisplayString(); - Assert.IsTrue(methodBody.Contains("timestamp = child.GetDateTimeOffset(\"O\")"), + Assert.IsTrue(methodBody.Contains("@on = child.GetDateTimeOffset(\"O\")"), $"DateTimeOffset property should use child.GetDateTimeOffset(\"O\") with RFC3339 format. Actual:\n{methodBody}"); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs index 91417351981..563329b08be 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs @@ -335,7 +335,7 @@ public void XmlSerializationHandlesDateTimeOffsetProperty() Assert.IsNotNull(xmlSerializationMethod); var methodBody = xmlSerializationMethod!.BodyStatements!.ToDisplayString(); - Assert.IsTrue(methodBody.Contains("WriteStringValue") && methodBody.Contains("Timestamp"), + Assert.IsTrue(methodBody.Contains("writer.WriteStringValue(On.Value, \"O\")"), $"DateTimeOffset property should be serialized with WriteStringValue. Actual:\n{methodBody}"); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index b253e910c6f..cf2024e6736 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -63,7 +63,9 @@ public sealed class ParameterProvider : IEquatable public ParameterProvider(InputParameter inputParameter) { InputParameter = inputParameter; - Name = inputParameter.Name; + Name = inputParameter is InputMethodParameter && !inputParameter.IsExactName + ? inputParameter.Name.NormalizeDateTimeSuffix(inputParameter.Type) + : inputParameter.Name; Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type) ?? throw new InvalidOperationException($"Failed to create CSharpType for {inputParameter.Type}"); if (!inputParameter.IsRequired) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs index b8dfe9fe276..3bf19d3cac5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs @@ -111,7 +111,9 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T (lastContractProperties is null || !lastContractProperties.Any(p => p.Name == legacyName))) { - identifierName = identifierName.NormalizeCSharpAcronyms(); + identifierName = identifierName + .NormalizeDateTimeSuffix(inputProperty.Type) + .NormalizeCSharpAcronyms(); } Name = identifierName == enclosingType.Name ? $"{identifierName}Property" diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpNameExtensions.cs index 39fb45aff2e..409792f9465 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpNameExtensions.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Text; +using Microsoft.TypeSpec.Generator.Input; namespace Microsoft.TypeSpec.Generator.Utilities { @@ -19,6 +21,18 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu ("Os", "OS") ]; + private static readonly Dictionary _dateTimeNounRenamingRules = new(StringComparer.Ordinal) + { + ["Creation"] = "Created", + ["creation"] = "created", + ["Deletion"] = "Deleted", + ["deletion"] = "deleted", + ["Expiration"] = "Expire", + ["expiration"] = "expire", + ["Modification"] = "Modified", + ["modification"] = "modified" + }; + public static string NormalizeCSharpAcronyms(this string name) { StringBuilder? normalizedName = null; @@ -55,5 +69,80 @@ public static string NormalizeCSharpAcronyms(this string name) normalizedName.Append(name, segmentStart, name.Length - segmentStart); return normalizedName.ToString(); } + + public static string NormalizeDateTimeSuffix(this string name, InputType inputType) + { + if (!IsDateTimeInputType(inputType) || + name.StartsWith("From", StringComparison.Ordinal) || + name.StartsWith("from", StringComparison.Ordinal) || + name.StartsWith("To", StringComparison.Ordinal) || + name.StartsWith("to", StringComparison.Ordinal) || + name.EndsWith("PointInTime", StringComparison.Ordinal) || + name.Equals("pointInTime", StringComparison.Ordinal)) + { + return name; + } + + var suffixLength = GetDateTimeSuffixLength(name); + if (suffixLength == 0) + { + return name; + } + + var prefix = name[..^suffixLength]; + if (_dateTimeNounRenamingRules.TryGetValue(prefix, out var replacement)) + { + prefix = replacement; + } + + var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? "on" : "On"; + return prefix + onSuffix; + } + + private static int GetDateTimeSuffixLength(string name) + { + if (name.EndsWith("Timestamp", StringComparison.Ordinal) || + name.EndsWith("TimeStamp", StringComparison.Ordinal)) + { + return 9; + } + + if (name.Equals("timestamp", StringComparison.Ordinal) || + name.Equals("timeStamp", StringComparison.Ordinal)) + { + return 9; + } + + if (name.Length > 8 && name.EndsWith("DateTime", StringComparison.Ordinal)) + { + return 8; + } + + if ((name.Length > 4 && name.EndsWith("Time", StringComparison.Ordinal)) || + name.EndsWith("Date", StringComparison.Ordinal)) + { + return 4; + } + + if (name.Equals("date", StringComparison.Ordinal)) + { + return 4; + } + + if (name.Length > 2 && name.EndsWith("At", StringComparison.Ordinal)) + { + return 2; + } + + return 0; + } + + private static bool IsDateTimeInputType(InputType inputType) => inputType switch + { + InputDateTimeType => true, + InputPrimitiveType { Kind: InputPrimitiveTypeKind.PlainDate } => true, + InputNullableType nullableType => IsDateTimeInputType(nullableType.Type), + _ => false + }; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index c4671db76a8..256e10d88d5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -67,6 +67,27 @@ public void ValidateArrayHandling() Assert.IsTrue(parameter.ToPublicInputParameter().Type.Equals(typeof(IEnumerable))); } + [TestCaseSource(nameof(DateTimeParameterNameTestCases))] + public void MethodParameterNameNormalizesDateTimeSuffix( + string inputName, + InputType inputType, + bool isExactName, + string expectedName) + { + MockHelpers.LoadMockGenerator(); + var inputParameter = InputFactory.MethodParameter( + inputName, + inputType, + isRequired: true, + isExactName: isExactName); + + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(inputParameter); + + Assert.IsNotNull(parameter); + Assert.AreEqual(expectedName, parameter!.Name); + Assert.AreEqual(inputName, parameter.WireInfo.SerializedName); + } + private static IEnumerable ValueInputTypes() { yield return InputPrimitiveType.Int32; @@ -74,6 +95,28 @@ private static IEnumerable ValueInputTypes() yield return InputFactory.Int32Enum("inputEnum", [("foo", 1)], isExtensible: true); } + private static IEnumerable DateTimeParameterNameTestCases() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + + yield return new TestCaseData("startTime", dateTime, false, "startOn"); + yield return new TestCaseData("creationTimestamp", dateTime, false, "createdOn"); + yield return new TestCaseData("timestamp", dateTime, false, "on"); + yield return new TestCaseData("date", InputPrimitiveType.PlainDate, false, "on"); + yield return new TestCaseData("modifiedAt", dateTime.WithNullable(true), false, "modifiedOn"); + yield return new TestCaseData("fromTime", dateTime, false, "fromTime"); + yield return new TestCaseData("toDate", dateTime, false, "toDate"); + yield return new TestCaseData("pointInTime", dateTime, false, "pointInTime"); + yield return new TestCaseData("recoveryPointInTime", dateTime, false, "recoveryPointInTime"); + yield return new TestCaseData("startTime", InputPrimitiveType.String, false, "startTime"); + yield return new TestCaseData("creationTimestamp", InputPrimitiveType.String, false, "creationTimestamp"); + yield return new TestCaseData("creationTimestamp", dateTime, true, "creationTimestamp"); + } + private static IEnumerable NotEqualsTestCases() { yield return new TestCaseData( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index 96c3fb419ca..617610a1dd6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -129,6 +129,55 @@ public void TestPropertyNameNormalizesAcronymCasing(string inputName, bool isExa Assert.AreEqual(expectedName, property.Name); } + [TestCaseSource(nameof(DateTimePropertyNameTestCases))] + public void TestPropertyNameNormalizesDateTimeSuffix( + string inputName, + InputType inputType, + bool isExactName, + string expectedName) + { + var inputProperty = InputFactory.Property( + inputName, + inputType, + isRequired: true, + isExactName: isExactName); + InputFactory.Model("TestModel", properties: [inputProperty]); + + var property = new PropertyProvider(inputProperty, new TestTypeProvider()); + + Assert.AreEqual(expectedName, property.Name); + Assert.AreEqual(inputName.ToVariableName(), property.WireInfo?.SerializedName); + } + + [Test] + public void TestPropertyNamePreservesLastContractDateTimeSuffix() + { + var lastContract = new TestTypeProvider(properties: + [ + new PropertyProvider( + description: null, + modifiers: MethodSignatureModifiers.Public, + type: typeof(DateTimeOffset), + name: "StartTime", + body: new AutoPropertyBody(HasSetter: true), + enclosingType: TestTypeProvider.Empty) + ]); + var enclosingType = new TestTypeProviderWithLastContract(lastContract); + var inputProperty = InputFactory.Property( + "StartTime", + new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String), + isRequired: true); + InputFactory.Model("TestModel", properties: [inputProperty]); + + var property = new PropertyProvider(inputProperty, enclosingType); + + Assert.AreEqual("StartTime", property.Name); + } + [TestCaseSource(nameof(CollectionPropertyTestCases))] public void CollectionProperty(CSharpType coreType, InputModelProperty collectionProperty, CSharpType expectedType) { @@ -202,6 +251,45 @@ public void TestPropertyNameConflictsWithTypeNameAfterAcronymNormalization() Assert.AreEqual("IPAddressProperty", property.Name); } + private static IEnumerable DateTimePropertyNameTestCases() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + + yield return new TestCaseData("StartTime", dateTime, false, "StartOn"); + yield return new TestCaseData("CreationDateTime", dateTime, false, "CreatedOn"); + yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletedOn"); + yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModifiedOn"); + yield return new TestCaseData("Timestamp", dateTime, false, "On"); + yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpireOn"); + yield return new TestCaseData("RecordedAt", dateTime, false, "RecordedOn"); + yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, false, "On"); + yield return new TestCaseData("SnapshotTimestamp", dateTime.WithNullable(true), false, "SnapshotOn"); + yield return new TestCaseData("FromTime", dateTime, false, "FromTime"); + yield return new TestCaseData("ToDate", dateTime, false, "ToDate"); + yield return new TestCaseData("RecoveryPointInTime", dateTime, false, "RecoveryPointInTime"); + yield return new TestCaseData("StartTime", InputPrimitiveType.String, false, "StartTime"); + yield return new TestCaseData("CreationTimestamp", InputPrimitiveType.String, false, "CreationTimestamp"); + yield return new TestCaseData("CreationTimestamp", dateTime, true, "CreationTimestamp"); + } + + private sealed class TestTypeProviderWithLastContract : TestTypeProvider + { + private readonly TypeProvider _lastContract; + + public TestTypeProviderWithLastContract(TypeProvider lastContract) + { + _lastContract = lastContract; + } + + private protected override TypeProvider? BuildLastContractView( + string? generatedTypeName = default, + string? generatedTypeNamespace = default) => _lastContract; + } + [Test] public void CanUpdatePropertyProvider() { diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.Serialization.cs index f1d688b9098..e1cf5add97d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.Serialization.cs @@ -226,7 +226,7 @@ internal virtual void XmlModelWriteCore(XmlWriter writer, ModelReaderWriterOptio } writer.WriteEndElement(); writer.WriteStartElement("createdAt"); - writer.WriteStringValue(CreatedAt, "O"); + writer.WriteStringValue(CreatedOn, "O"); writer.WriteEndElement(); writer.WriteStartElement("duration"); writer.WriteStringValue(Duration, "P"); @@ -390,7 +390,7 @@ internal static XmlAdvancedModel DeserializeXmlAdvancedModel(XElement element, M XmlNestedModel nestedModel = default; XmlNestedModel optionalNestedModel = default; IDictionary metadata = default; - DateTimeOffset createdAt = default; + DateTimeOffset createdOn = default; TimeSpan duration = default; BinaryData data = default; IDictionary optionalRecordUnknown = default; @@ -544,7 +544,7 @@ internal static XmlAdvancedModel DeserializeXmlAdvancedModel(XElement element, M } if (localName == "createdAt") { - createdAt = child.GetDateTimeOffset("O"); + createdOn = child.GetDateTimeOffset("O"); continue; } if (localName == "duration") @@ -717,7 +717,7 @@ internal static XmlAdvancedModel DeserializeXmlAdvancedModel(XElement element, M nestedModel, optionalNestedModel, metadata, - createdAt, + createdOn, duration, data, optionalRecordUnknown ?? new ChangeTrackingDictionary(), diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.cs index 32a82daf882..e52d1f3f9d5 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/XmlAdvancedModel.cs @@ -34,7 +34,7 @@ public partial class XmlAdvancedModel /// A wrapped array with custom wrapper name. /// A nested model property. /// A dictionary property. - /// A date-time property. + /// A date-time property. /// A duration property. /// A bytes property. /// A fixed enum property. @@ -51,7 +51,7 @@ public partial class XmlAdvancedModel /// /// /// , , , , , , , , , , , , , , , , , , , , , or is null. - public XmlAdvancedModel(string name, int age, bool enabled, float score, string nullableString, string id, int version, bool isActive, string originalName, string xmlIdentifier, string content, IEnumerable unwrappedStrings, IEnumerable unwrappedCounts, IEnumerable unwrappedItems, IEnumerable wrappedColors, IEnumerable items, XmlNestedModel nestedModel, IDictionary metadata, DateTimeOffset createdAt, TimeSpan duration, BinaryData data, StringFixedEnum fixedEnum, StringExtensibleEnum extensibleEnum, string label, int daysUsed, IEnumerable fooItems, XmlNestedModel anotherModel, IEnumerable modelsWithNamespaces, IEnumerable unwrappedModelsWithNamespaces, IEnumerable> listOfListFoo, IDictionary dictionaryFoo, IDictionary> dictionaryOfDictionaryFoo, IDictionary> dictionaryListFoo, IEnumerable> listOfDictionaryFoo) + public XmlAdvancedModel(string name, int age, bool enabled, float score, string nullableString, string id, int version, bool isActive, string originalName, string xmlIdentifier, string content, IEnumerable unwrappedStrings, IEnumerable unwrappedCounts, IEnumerable unwrappedItems, IEnumerable wrappedColors, IEnumerable items, XmlNestedModel nestedModel, IDictionary metadata, DateTimeOffset createdOn, TimeSpan duration, BinaryData data, StringFixedEnum fixedEnum, StringExtensibleEnum extensibleEnum, string label, int daysUsed, IEnumerable fooItems, XmlNestedModel anotherModel, IEnumerable modelsWithNamespaces, IEnumerable unwrappedModelsWithNamespaces, IEnumerable> listOfListFoo, IDictionary dictionaryFoo, IDictionary> dictionaryOfDictionaryFoo, IDictionary> dictionaryListFoo, IEnumerable> listOfDictionaryFoo) { Argument.AssertNotNull(name, nameof(name)); Argument.AssertNotNull(id, nameof(id)); @@ -95,7 +95,7 @@ public XmlAdvancedModel(string name, int age, bool enabled, float score, string Items = items.ToList(); NestedModel = nestedModel; Metadata = metadata; - CreatedAt = createdAt; + CreatedOn = createdOn; Duration = duration; Data = data; OptionalRecordUnknown = new ChangeTrackingDictionary(); @@ -136,7 +136,7 @@ public XmlAdvancedModel(string name, int age, bool enabled, float score, string /// A nested model property. /// An optional nested model. /// A dictionary property. - /// A date-time property. + /// A date-time property. /// A duration property. /// A bytes property. /// optional record of unknown. @@ -155,7 +155,7 @@ public XmlAdvancedModel(string name, int age, bool enabled, float score, string /// /// /// - internal XmlAdvancedModel(string name, int age, bool enabled, float score, string optionalString, int? optionalInt, string nullableString, string id, int version, bool isActive, string originalName, string xmlIdentifier, string content, IList unwrappedStrings, IList unwrappedCounts, IList unwrappedItems, IList wrappedColors, IList items, XmlNestedModel nestedModel, XmlNestedModel optionalNestedModel, IDictionary metadata, DateTimeOffset createdAt, TimeSpan duration, BinaryData data, IDictionary optionalRecordUnknown, StringFixedEnum fixedEnum, StringExtensibleEnum extensibleEnum, IntFixedEnum? optionalFixedEnum, IntExtensibleEnum? optionalExtensibleEnum, string label, int daysUsed, IList fooItems, XmlNestedModel anotherModel, IList modelsWithNamespaces, IList unwrappedModelsWithNamespaces, IList> listOfListFoo, IDictionary dictionaryFoo, IDictionary> dictionaryOfDictionaryFoo, IDictionary> dictionaryListFoo, IList> listOfDictionaryFoo) + internal XmlAdvancedModel(string name, int age, bool enabled, float score, string optionalString, int? optionalInt, string nullableString, string id, int version, bool isActive, string originalName, string xmlIdentifier, string content, IList unwrappedStrings, IList unwrappedCounts, IList unwrappedItems, IList wrappedColors, IList items, XmlNestedModel nestedModel, XmlNestedModel optionalNestedModel, IDictionary metadata, DateTimeOffset createdOn, TimeSpan duration, BinaryData data, IDictionary optionalRecordUnknown, StringFixedEnum fixedEnum, StringExtensibleEnum extensibleEnum, IntFixedEnum? optionalFixedEnum, IntExtensibleEnum? optionalExtensibleEnum, string label, int daysUsed, IList fooItems, XmlNestedModel anotherModel, IList modelsWithNamespaces, IList unwrappedModelsWithNamespaces, IList> listOfListFoo, IDictionary dictionaryFoo, IDictionary> dictionaryOfDictionaryFoo, IDictionary> dictionaryListFoo, IList> listOfDictionaryFoo) { Name = name; Age = age; @@ -178,7 +178,7 @@ internal XmlAdvancedModel(string name, int age, bool enabled, float score, strin NestedModel = nestedModel; OptionalNestedModel = optionalNestedModel; Metadata = metadata; - CreatedAt = createdAt; + CreatedOn = createdOn; Duration = duration; Data = data; OptionalRecordUnknown = optionalRecordUnknown; @@ -263,7 +263,7 @@ internal XmlAdvancedModel(string name, int age, bool enabled, float score, strin public IDictionary Metadata { get; } /// A date-time property. - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset CreatedOn { get; set; } /// A duration property. public TimeSpan Duration { get; set; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs index 3a65a21dc61..65fc0e6464a 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs @@ -270,7 +270,7 @@ public static AnotherDynamicModel AnotherDynamicModel(string bar = default) /// A nested model property. /// An optional nested model. /// A dictionary property. - /// A date-time property. + /// A date-time property. /// A duration property. /// A bytes property. /// optional record of unknown. @@ -290,7 +290,7 @@ public static AnotherDynamicModel AnotherDynamicModel(string bar = default) /// /// /// A new instance for mocking. - public static XmlAdvancedModel XmlAdvancedModel(string name = default, int age = default, bool enabled = default, float score = default, string optionalString = default, int? optionalInt = default, string nullableString = default, string id = default, int version = default, bool isActive = default, string originalName = default, string xmlIdentifier = default, string content = default, IEnumerable unwrappedStrings = default, IEnumerable unwrappedCounts = default, IEnumerable unwrappedItems = default, IEnumerable wrappedColors = default, IEnumerable items = default, XmlNestedModel nestedModel = default, XmlNestedModel optionalNestedModel = default, IDictionary metadata = default, DateTimeOffset createdAt = default, TimeSpan duration = default, BinaryData data = default, IDictionary optionalRecordUnknown = default, StringFixedEnum fixedEnum = default, StringExtensibleEnum extensibleEnum = default, IntFixedEnum? optionalFixedEnum = default, IntExtensibleEnum? optionalExtensibleEnum = default, string label = default, int daysUsed = default, IEnumerable fooItems = default, XmlNestedModel anotherModel = default, IEnumerable modelsWithNamespaces = default, IEnumerable unwrappedModelsWithNamespaces = default, IEnumerable> listOfListFoo = default, IDictionary dictionaryFoo = default, IDictionary> dictionaryOfDictionaryFoo = default, IDictionary> dictionaryListFoo = default, IEnumerable> listOfDictionaryFoo = default) + public static XmlAdvancedModel XmlAdvancedModel(string name = default, int age = default, bool enabled = default, float score = default, string optionalString = default, int? optionalInt = default, string nullableString = default, string id = default, int version = default, bool isActive = default, string originalName = default, string xmlIdentifier = default, string content = default, IEnumerable unwrappedStrings = default, IEnumerable unwrappedCounts = default, IEnumerable unwrappedItems = default, IEnumerable wrappedColors = default, IEnumerable items = default, XmlNestedModel nestedModel = default, XmlNestedModel optionalNestedModel = default, IDictionary metadata = default, DateTimeOffset createdOn = default, TimeSpan duration = default, BinaryData data = default, IDictionary optionalRecordUnknown = default, StringFixedEnum fixedEnum = default, StringExtensibleEnum extensibleEnum = default, IntFixedEnum? optionalFixedEnum = default, IntExtensibleEnum? optionalExtensibleEnum = default, string label = default, int daysUsed = default, IEnumerable fooItems = default, XmlNestedModel anotherModel = default, IEnumerable modelsWithNamespaces = default, IEnumerable unwrappedModelsWithNamespaces = default, IEnumerable> listOfListFoo = default, IDictionary dictionaryFoo = default, IDictionary> dictionaryOfDictionaryFoo = default, IDictionary> dictionaryListFoo = default, IEnumerable> listOfDictionaryFoo = default) { unwrappedStrings ??= new ChangeTrackingList(); unwrappedCounts ??= new ChangeTrackingList(); @@ -330,7 +330,7 @@ public static XmlAdvancedModel XmlAdvancedModel(string name = default, int age = nestedModel, optionalNestedModel, metadata, - createdAt, + createdOn, duration, data, optionalRecordUnknown, From 9b0729de6fbe83772ef17d4d2dd4250f4817dc1d Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Thu, 13 Aug 2026 16:54:15 -0500 Subject: [PATCH 02/22] shorten exclusion list --- .../src/Shared/CSharpNameExtensions.cs | 32 +++++++------------ .../test/Providers/ParameterProviderTests.cs | 2 +- .../test/Providers/PropertyProviderTests.cs | 8 ++--- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index 959d82faa82..1dbe5a675d5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -22,16 +22,10 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu ("Os", "OS") ]; - private static readonly Dictionary _dateTimeNounRenamingRules = new(StringComparer.Ordinal) + private static readonly HashSet _dateTimePrefixExclusions = new(StringComparer.OrdinalIgnoreCase) { - ["Creation"] = "Created", - ["creation"] = "created", - ["Deletion"] = "Deleted", - ["deletion"] = "deleted", - ["Expiration"] = "Expire", - ["expiration"] = "expire", - ["Modification"] = "Modified", - ["modification"] = "modified" + "From", + "To" }; public static string NormalizeCSharpAcronyms(this string name) @@ -74,12 +68,8 @@ public static string NormalizeCSharpAcronyms(this string name) public static string NormalizeDateTimeSuffix(this string name, InputType inputType) { if (!IsDateTimeInputType(inputType) || - name.StartsWith("From", StringComparison.Ordinal) || - name.StartsWith("from", StringComparison.Ordinal) || - name.StartsWith("To", StringComparison.Ordinal) || - name.StartsWith("to", StringComparison.Ordinal) || - name.EndsWith("PointInTime", StringComparison.Ordinal) || - name.Equals("pointInTime", StringComparison.Ordinal)) + HasExcludedDateTimePrefix(name) || + name.EndsWith("PointInTime", StringComparison.OrdinalIgnoreCase)) { return name; } @@ -91,15 +81,17 @@ public static string NormalizeDateTimeSuffix(this string name, InputType inputTy } var prefix = name[..^suffixLength]; - if (_dateTimeNounRenamingRules.TryGetValue(prefix, out var replacement)) - { - prefix = replacement; - } - var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? "on" : "On"; return prefix + onSuffix; } + private static bool HasExcludedDateTimePrefix(string name) + { + var lookup = _dateTimePrefixExclusions.GetAlternateLookup>(); + return (name.Length >= 4 && lookup.Contains(name.AsSpan(0, 4))) || + (name.Length >= 2 && lookup.Contains(name.AsSpan(0, 2))); + } + private static int GetDateTimeSuffixLength(string name) { if (name.EndsWith("Timestamp", StringComparison.Ordinal) || diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 256e10d88d5..2cd28132ecc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -104,7 +104,7 @@ private static IEnumerable DateTimeParameterNameTestCases() InputPrimitiveType.String); yield return new TestCaseData("startTime", dateTime, false, "startOn"); - yield return new TestCaseData("creationTimestamp", dateTime, false, "createdOn"); + yield return new TestCaseData("creationTimestamp", dateTime, false, "creationOn"); yield return new TestCaseData("timestamp", dateTime, false, "on"); yield return new TestCaseData("date", InputPrimitiveType.PlainDate, false, "on"); yield return new TestCaseData("modifiedAt", dateTime.WithNullable(true), false, "modifiedOn"); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index 617610a1dd6..d245dec0734 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -260,11 +260,11 @@ private static IEnumerable DateTimePropertyNameTestCases() InputPrimitiveType.String); yield return new TestCaseData("StartTime", dateTime, false, "StartOn"); - yield return new TestCaseData("CreationDateTime", dateTime, false, "CreatedOn"); - yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletedOn"); - yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModifiedOn"); + yield return new TestCaseData("CreationDateTime", dateTime, false, "CreationOn"); + yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletionOn"); + yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModificationOn"); yield return new TestCaseData("Timestamp", dateTime, false, "On"); - yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpireOn"); + yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpirationOn"); yield return new TestCaseData("RecordedAt", dateTime, false, "RecordedOn"); yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, false, "On"); yield return new TestCaseData("SnapshotTimestamp", dateTime.WithNullable(true), false, "SnapshotOn"); From 8bde832df216a198f8c32072953d58953f5ce45b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:48:20 +0000 Subject: [PATCH 03/22] fix: cover date time name exclusions Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Shared/CSharpNameExtensions.cs | 15 ++++++----- .../Utilities/CSharpNameExtensionsTests.cs | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index 1dbe5a675d5..da48bdaf360 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -22,10 +22,11 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu ("Os", "OS") ]; - private static readonly HashSet _dateTimePrefixExclusions = new(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet _dateTimeNameExclusions = new(StringComparer.OrdinalIgnoreCase) { "From", - "To" + "To", + "PointInTime" }; public static string NormalizeCSharpAcronyms(this string name) @@ -68,8 +69,7 @@ public static string NormalizeCSharpAcronyms(this string name) public static string NormalizeDateTimeSuffix(this string name, InputType inputType) { if (!IsDateTimeInputType(inputType) || - HasExcludedDateTimePrefix(name) || - name.EndsWith("PointInTime", StringComparison.OrdinalIgnoreCase)) + HasExcludedDateTimeNameComponent(name)) { return name; } @@ -85,11 +85,12 @@ public static string NormalizeDateTimeSuffix(this string name, InputType inputTy return prefix + onSuffix; } - private static bool HasExcludedDateTimePrefix(string name) + private static bool HasExcludedDateTimeNameComponent(string name) { - var lookup = _dateTimePrefixExclusions.GetAlternateLookup>(); + var lookup = _dateTimeNameExclusions.GetAlternateLookup>(); return (name.Length >= 4 && lookup.Contains(name.AsSpan(0, 4))) || - (name.Length >= 2 && lookup.Contains(name.AsSpan(0, 2))); + (name.Length >= 2 && lookup.Contains(name.AsSpan(0, 2))) || + (name.Length >= "PointInTime".Length && lookup.Contains(name.AsSpan(^"PointInTime".Length))); } private static int GetDateTimeSuffixLength(string name) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs index 1e84328cb7f..25c54e0b0fc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Collections.Generic; +using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Utilities; using NUnit.Framework; @@ -19,5 +21,29 @@ public void TestNormalizeCSharpUrlSuffix(string name, string expected) { Assert.AreEqual(expected, name.NormalizeCSharpUrlSuffix()); } + + [TestCaseSource(nameof(DateTimeNameTestCases))] + public void TestNormalizeDateTimeSuffix(string name, InputType type, string expected) + { + Assert.AreEqual(expected, name.NormalizeDateTimeSuffix(type)); + } + + private static IEnumerable DateTimeNameTestCases() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + + yield return new TestCaseData("startTime", dateTime, "startOn"); + yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, "On"); + yield return new TestCaseData("date", InputPrimitiveType.PlainDate, "on"); + yield return new TestCaseData("fromTime", dateTime, "fromTime"); + yield return new TestCaseData("toDate", dateTime, "toDate"); + yield return new TestCaseData("pointInTime", dateTime, "pointInTime"); + yield return new TestCaseData("recoveryPointInTime", dateTime, "recoveryPointInTime"); + yield return new TestCaseData("startTime", InputPrimitiveType.String, "startTime"); + } } } From 875ac14e72c20df3f976dd2af09cdc92723d000a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:47:52 +0000 Subject: [PATCH 04/22] fix: preserve normalized parameter names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Utilities/BackCompatHelper.cs | 6 ++++- .../TestClient.cs | 7 ++++++ .../test/Providers/TypeProviderTests.cs | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 979d40194ca..e8bd8c2f96b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Input.Extensions; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; @@ -189,7 +190,10 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null && string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal)) + if (inputParameter is not null && + (string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal) || + (inputParameter is InputMethodParameter { IsExactName: false } && + string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(inputParameter.Type), StringComparison.Ordinal)))) { var originalName = inputParameter.OriginalName; if (!string.IsNullOrEmpty(originalName)) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName/TestClient.cs new file mode 100644 index 00000000000..a38628fee6c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName/TestClient.cs @@ -0,0 +1,7 @@ +namespace Test +{ + public class TestClient + { + public string Foo(string startTime) { return null; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index b4b6d14584d..b8948ca88de 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -786,6 +786,28 @@ public void FindPreviousParameterNameReturnsNullWithoutLastContract() Assert.IsNull(BackCompatHelper.FindPreviousParameterName(typeProvider.LastContractView, "oldParam", "Foo")); } + [Test] + public async Task RestorePreviousParameterNamesRestoresNormalizedMethodParameterName() + { + await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var parameter = new ParameterProvider(InputFactory.MethodParameter("startTime", dateTime, isRequired: true)); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, new CSharpType(typeof(string)), $"", [parameter]), + Snippet.Return(Snippet.Null), + new TestTypeProvider()); + var typeProvider = new TestTypeProvider(name: "TestClient", methods: [method]); + + BackCompatHelper.RestorePreviousParameterNames(typeProvider, typeProvider.Methods); + + Assert.AreEqual("startTime", parameter.Name); + } + // A reorder combined with a casing-only rename must still restore the published spelling. // The reorder matches parameters via ToVariableName (so current "URL" aligns with published // "url"), which previously short-circuited the exact-name pass and left the CP0017-breaking From aa00bd30bfa0e24445901459aa243451371ba704 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:30:22 +0000 Subject: [PATCH 05/22] fix: address date naming review feedback Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../XmlDeserializationTests.cs | 4 +- .../src/Providers/ParameterProvider.cs | 3 +- .../src/Providers/PropertyProvider.cs | 3 +- .../src/Shared/CSharpNameExtensions.cs | 79 ++++++++++++------- .../src/Utilities/BackCompatHelper.cs | 3 +- .../test/Providers/ParameterProviderTests.cs | 4 +- .../test/Providers/PropertyProviderTests.cs | 51 +++++------- ...vesLastContractDateTimeSuffix(Expected).cs | 27 +++++++ .../TestModel.cs | 7 ++ ...NormalizedMethodParameterName(Expected).cs | 16 ++++ .../test/Providers/TypeProviderTests.cs | 3 +- .../Utilities/CSharpNameExtensionsTests.cs | 14 +++- 12 files changed, 142 insertions(+), 72 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix(Expected).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix/TestModel.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName(Expected).cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index 52666216903..d0ae40f8f84 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -261,8 +261,8 @@ public void XmlDeserializationHandlesDateTimeOffsetProperty() Assert.IsNotNull(xmlDeserializationMethod); var methodBody = xmlDeserializationMethod!.BodyStatements!.ToDisplayString(); - Assert.IsTrue(methodBody.Contains("@on = child.GetDateTimeOffset(\"O\")"), - $"DateTimeOffset property should use child.GetDateTimeOffset(\"O\") with RFC3339 format. Actual:\n{methodBody}"); + Assert.IsTrue(methodBody.Contains("GetDateTimeOffset(\"O\")"), + $"DateTimeOffset property should use RFC3339 format. Actual:\n{methodBody}"); } [Test] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index cf2024e6736..496612af7d3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -64,7 +64,8 @@ public ParameterProvider(InputParameter inputParameter) { InputParameter = inputParameter; Name = inputParameter is InputMethodParameter && !inputParameter.IsExactName - ? inputParameter.Name.NormalizeDateTimeSuffix(inputParameter.Type) + && inputParameter.Type.IsDateTimeInputType() + ? inputParameter.Name.NormalizeDateTimeSuffix() : inputParameter.Name; Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type) ?? throw new InvalidOperationException($"Failed to create CSharpType for {inputParameter.Type}"); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs index 3bf19d3cac5..4984219b395 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/PropertyProvider.cs @@ -112,8 +112,7 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T !lastContractProperties.Any(p => p.Name == legacyName))) { identifierName = identifierName - .NormalizeDateTimeSuffix(inputProperty.Type) - .NormalizeCSharpAcronyms(); + .NormalizeCSharpAcronyms(inputProperty.Type.IsDateTimeInputType()); } Name = identifierName == enclosingType.Name ? $"{identifierName}Property" diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index da48bdaf360..c9d9a40b103 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -11,6 +11,18 @@ namespace Microsoft.TypeSpec.Generator.Utilities { internal static class CSharpNameExtensions { + private const string DateSuffix = "Date"; + private const string DateTimeSuffix = "DateTime"; + private const string FromName = "From"; + private const string LowercaseOnSuffix = "on"; + private const string OnSuffix = "On"; + private const string PointInTimeName = "PointInTime"; + private const string TimeStampSuffix = "TimeStamp"; + private const string TimeSuffix = "Time"; + private const string TimestampSuffix = "Timestamp"; + private const string ToName = "To"; + private const string AtSuffix = "At"; + private static readonly (string Source, string Replacement)[] _acronymRenamingRules = [ ("Ipv4", "IPv4"), @@ -24,14 +36,17 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu private static readonly HashSet _dateTimeNameExclusions = new(StringComparer.OrdinalIgnoreCase) { - "From", - "To", - "PointInTime" + FromName, + ToName, + PointInTimeName }; - public static string NormalizeCSharpAcronyms(this string name) + public static string NormalizeCSharpAcronyms(this string name, bool normalizeDateTimeSuffix = false) { - StringBuilder? normalizedName = null; + var suffixLength = normalizeDateTimeSuffix && !HasExcludedDateTimeNameComponent(name) + ? GetDateTimeSuffixLength(name) + : 0; + StringBuilder? normalizedName = suffixLength > 0 ? new(name.Length - suffixLength + OnSuffix.Length) : null; int segmentStart = 0; for (int index = 0; index < name.Length - 1; index++) { @@ -62,14 +77,17 @@ public static string NormalizeCSharpAcronyms(this string name) return name; } - normalizedName.Append(name, segmentStart, name.Length - segmentStart); + normalizedName.Append(name, segmentStart, name.Length - suffixLength - segmentStart); + if (suffixLength > 0) + { + normalizedName.Append(name.Length == suffixLength && char.IsLower(name[0]) ? LowercaseOnSuffix : OnSuffix); + } return normalizedName.ToString(); } - public static string NormalizeDateTimeSuffix(this string name, InputType inputType) + public static string NormalizeDateTimeSuffix(this string name) { - if (!IsDateTimeInputType(inputType) || - HasExcludedDateTimeNameComponent(name)) + if (HasExcludedDateTimeNameComponent(name)) { return name; } @@ -81,57 +99,60 @@ public static string NormalizeDateTimeSuffix(this string name, InputType inputTy } var prefix = name[..^suffixLength]; - var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? "on" : "On"; + var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? LowercaseOnSuffix : OnSuffix; return prefix + onSuffix; } private static bool HasExcludedDateTimeNameComponent(string name) { var lookup = _dateTimeNameExclusions.GetAlternateLookup>(); - return (name.Length >= 4 && lookup.Contains(name.AsSpan(0, 4))) || - (name.Length >= 2 && lookup.Contains(name.AsSpan(0, 2))) || - (name.Length >= "PointInTime".Length && lookup.Contains(name.AsSpan(^"PointInTime".Length))); + return (name.Length >= FromName.Length && lookup.Contains(name.AsSpan(0, FromName.Length))) || + (name.Length >= ToName.Length && lookup.Contains(name.AsSpan(0, ToName.Length))) || + (name.Length >= PointInTimeName.Length && lookup.Contains(name.AsSpan(^PointInTimeName.Length))); } private static int GetDateTimeSuffixLength(string name) { - if (name.EndsWith("Timestamp", StringComparison.Ordinal) || - name.EndsWith("TimeStamp", StringComparison.Ordinal)) + if (name.EndsWith(TimestampSuffix, StringComparison.Ordinal) || + name.EndsWith(TimeStampSuffix, StringComparison.Ordinal)) + { + return TimestampSuffix.Length; + } + + if (name.Equals(TimestampSuffix, StringComparison.OrdinalIgnoreCase)) { - return 9; + return TimestampSuffix.Length; } - if (name.Equals("timestamp", StringComparison.Ordinal) || - name.Equals("timeStamp", StringComparison.Ordinal)) + if (name.Length > DateTimeSuffix.Length && name.EndsWith(DateTimeSuffix, StringComparison.Ordinal)) { - return 9; + return DateTimeSuffix.Length; } - if (name.Length > 8 && name.EndsWith("DateTime", StringComparison.Ordinal)) + if (name.Length > TimeSuffix.Length && name.EndsWith(TimeSuffix, StringComparison.Ordinal)) { - return 8; + return TimeSuffix.Length; } - if ((name.Length > 4 && name.EndsWith("Time", StringComparison.Ordinal)) || - name.EndsWith("Date", StringComparison.Ordinal)) + if (name.Equals(DateSuffix, StringComparison.OrdinalIgnoreCase)) { - return 4; + return DateSuffix.Length; } - if (name.Equals("date", StringComparison.Ordinal)) + if (name.EndsWith(DateSuffix, StringComparison.Ordinal)) { - return 4; + return DateSuffix.Length; } - if (name.Length > 2 && name.EndsWith("At", StringComparison.Ordinal)) + if (name.Length > AtSuffix.Length && name.EndsWith(AtSuffix, StringComparison.Ordinal)) { - return 2; + return AtSuffix.Length; } return 0; } - private static bool IsDateTimeInputType(InputType inputType) => inputType switch + public static bool IsDateTimeInputType(this InputType inputType) => inputType switch { InputDateTimeType => true, InputPrimitiveType { Kind: InputPrimitiveTypeKind.PlainDate } => true, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index e8bd8c2f96b..c30d1aa2e44 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -193,7 +193,8 @@ public static void RestorePreviousParameterNames( if (inputParameter is not null && (string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal) || (inputParameter is InputMethodParameter { IsExactName: false } && - string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(inputParameter.Type), StringComparison.Ordinal)))) + inputParameter.Type.IsDateTimeInputType() && + string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(), StringComparison.Ordinal)))) { var originalName = inputParameter.OriginalName; if (!string.IsNullOrEmpty(originalName)) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 2cd28132ecc..e39797743e9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -104,7 +104,7 @@ private static IEnumerable DateTimeParameterNameTestCases() InputPrimitiveType.String); yield return new TestCaseData("startTime", dateTime, false, "startOn"); - yield return new TestCaseData("creationTimestamp", dateTime, false, "creationOn"); + yield return new TestCaseData("createdAt", dateTime, false, "createdOn"); yield return new TestCaseData("timestamp", dateTime, false, "on"); yield return new TestCaseData("date", InputPrimitiveType.PlainDate, false, "on"); yield return new TestCaseData("modifiedAt", dateTime.WithNullable(true), false, "modifiedOn"); @@ -114,7 +114,7 @@ private static IEnumerable DateTimeParameterNameTestCases() yield return new TestCaseData("recoveryPointInTime", dateTime, false, "recoveryPointInTime"); yield return new TestCaseData("startTime", InputPrimitiveType.String, false, "startTime"); yield return new TestCaseData("creationTimestamp", InputPrimitiveType.String, false, "creationTimestamp"); - yield return new TestCaseData("creationTimestamp", dateTime, true, "creationTimestamp"); + yield return new TestCaseData("createdAt", dateTime, true, "createdAt"); } private static IEnumerable NotEqualsTestCases() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index d245dec0734..1f289017aba 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Input.Extensions; using Microsoft.TypeSpec.Generator.Primitives; @@ -150,32 +151,29 @@ public void TestPropertyNameNormalizesDateTimeSuffix( } [Test] - public void TestPropertyNamePreservesLastContractDateTimeSuffix() + public async Task TestPropertyNamePreservesLastContractDateTimeSuffix() { - var lastContract = new TestTypeProvider(properties: - [ - new PropertyProvider( - description: null, - modifiers: MethodSignatureModifiers.Public, - type: typeof(DateTimeOffset), - name: "StartTime", - body: new AutoPropertyBody(HasSetter: true), - enclosingType: TestTypeProvider.Empty) - ]); - var enclosingType = new TestTypeProviderWithLastContract(lastContract); - var inputProperty = InputFactory.Property( - "StartTime", - new InputDateTimeType( + await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var inputModel = InputFactory.Model( + "TestModel", + @namespace: "Test", + properties: + [ + InputFactory.Property( + "StartTime", + new InputDateTimeType( DateTimeKnownEncoding.Rfc3339, "utcDateTime", "TypeSpec.utcDateTime", InputPrimitiveType.String), - isRequired: true); - InputFactory.Model("TestModel", properties: [inputProperty]); + isRequired: true) + ]); - var property = new PropertyProvider(inputProperty, enclosingType); + var modelProvider = new ModelProvider(inputModel); + var actual = new TypeProviderWriter(modelProvider).Write().Content; - Assert.AreEqual("StartTime", property.Name); + Assert.AreEqual(Helpers.GetExpectedFromFile("Expected"), actual); } [TestCaseSource(nameof(CollectionPropertyTestCases))] @@ -260,7 +258,7 @@ private static IEnumerable DateTimePropertyNameTestCases() InputPrimitiveType.String); yield return new TestCaseData("StartTime", dateTime, false, "StartOn"); - yield return new TestCaseData("CreationDateTime", dateTime, false, "CreationOn"); + yield return new TestCaseData("CreatedAt", dateTime, false, "CreatedOn"); yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletionOn"); yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModificationOn"); yield return new TestCaseData("Timestamp", dateTime, false, "On"); @@ -276,19 +274,6 @@ private static IEnumerable DateTimePropertyNameTestCases() yield return new TestCaseData("CreationTimestamp", dateTime, true, "CreationTimestamp"); } - private sealed class TestTypeProviderWithLastContract : TestTypeProvider - { - private readonly TypeProvider _lastContract; - - public TestTypeProviderWithLastContract(TypeProvider lastContract) - { - _lastContract = lastContract; - } - - private protected override TypeProvider? BuildLastContractView( - string? generatedTypeName = default, - string? generatedTypeNamespace = default) => _lastContract; - } [Test] public void CanUpdatePropertyProvider() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix(Expected).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix(Expected).cs new file mode 100644 index 00000000000..2bc79bbeadc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix(Expected).cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System; +using System.Collections.Generic; + +namespace Test +{ + public partial class TestModel + { + private protected readonly global::System.Collections.Generic.IDictionary _additionalBinaryDataProperties; + + public TestModel(global::System.DateTimeOffset startTime) + { + StartTime = startTime; + } + + internal TestModel(global::System.DateTimeOffset startTime, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + { + StartTime = startTime; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + public global::System.DateTimeOffset StartTime { get; set; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix/TestModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix/TestModel.cs new file mode 100644 index 00000000000..d3860bb862d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/PropertyProviderTests/TestPropertyNamePreservesLastContractDateTimeSuffix/TestModel.cs @@ -0,0 +1,7 @@ +namespace Test +{ + public class TestModel + { + public System.DateTimeOffset StartTime { get; set; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName(Expected).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName(Expected).cs new file mode 100644 index 00000000000..88494a19a32 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/RestorePreviousParameterNamesRestoresNormalizedMethodParameterName(Expected).cs @@ -0,0 +1,16 @@ +// + +#nullable disable + +using System; + +namespace Test +{ + public partial class TestClient + { + public string Foo(global::System.DateTimeOffset startTime) + { + return null; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index b8948ca88de..303f8157a8a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -805,7 +805,8 @@ public async Task RestorePreviousParameterNamesRestoresNormalizedMethodParameter BackCompatHelper.RestorePreviousParameterNames(typeProvider, typeProvider.Methods); - Assert.AreEqual("startTime", parameter.Name); + var actual = new TypeProviderWriter(typeProvider).Write().Content; + Assert.AreEqual(Helpers.GetExpectedFromFile("Expected"), actual); } // A reorder combined with a casing-only rename must still restore the published spelling. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs index 25c54e0b0fc..3cf3937eac2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs @@ -25,7 +25,13 @@ public void TestNormalizeCSharpUrlSuffix(string name, string expected) [TestCaseSource(nameof(DateTimeNameTestCases))] public void TestNormalizeDateTimeSuffix(string name, InputType type, string expected) { - Assert.AreEqual(expected, name.NormalizeDateTimeSuffix(type)); + Assert.AreEqual(expected, type.IsDateTimeInputType() ? name.NormalizeDateTimeSuffix() : name); + } + + [Test] + public void NormalizeCSharpAcronymsNormalizesDateTimeSuffixInSinglePass() + { + Assert.AreEqual("IPStartOn", "IpStartTime".NormalizeCSharpAcronyms(normalizeDateTimeSuffix: true)); } private static IEnumerable DateTimeNameTestCases() @@ -44,6 +50,12 @@ private static IEnumerable DateTimeNameTestCases() yield return new TestCaseData("pointInTime", dateTime, "pointInTime"); yield return new TestCaseData("recoveryPointInTime", dateTime, "recoveryPointInTime"); yield return new TestCaseData("startTime", InputPrimitiveType.String, "startTime"); + yield return new TestCaseData("createdAt", dateTime, "createdOn"); + yield return new TestCaseData("expiresAt", dateTime, "expiresOn"); + yield return new TestCaseData("deletedTime", dateTime, "deletedOn"); + yield return new TestCaseData("finishedTime", dateTime, "finishedOn"); + yield return new TestCaseData("stateTransitionTime", dateTime, "stateTransitionOn"); + yield return new TestCaseData("notBefore", dateTime, "notBefore"); } } } From ea2410205e6ae978329d42a91d241f636dba959a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:55:58 +0000 Subject: [PATCH 06/22] fix: simplify date naming normalization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Shared/CSharpNameExtensions.cs | 117 +++++++----------- .../src/Utilities/BackCompatHelper.cs | 11 +- 2 files changed, 50 insertions(+), 78 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index c9d9a40b103..f1896e15e89 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.TypeSpec.Generator.Input; @@ -11,18 +10,6 @@ namespace Microsoft.TypeSpec.Generator.Utilities { internal static class CSharpNameExtensions { - private const string DateSuffix = "Date"; - private const string DateTimeSuffix = "DateTime"; - private const string FromName = "From"; - private const string LowercaseOnSuffix = "on"; - private const string OnSuffix = "On"; - private const string PointInTimeName = "PointInTime"; - private const string TimeStampSuffix = "TimeStamp"; - private const string TimeSuffix = "Time"; - private const string TimestampSuffix = "Timestamp"; - private const string ToName = "To"; - private const string AtSuffix = "At"; - private static readonly (string Source, string Replacement)[] _acronymRenamingRules = [ ("Ipv4", "IPv4"), @@ -34,19 +21,10 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu ("Os", "OS") ]; - private static readonly HashSet _dateTimeNameExclusions = new(StringComparer.OrdinalIgnoreCase) - { - FromName, - ToName, - PointInTimeName - }; - public static string NormalizeCSharpAcronyms(this string name, bool normalizeDateTimeSuffix = false) { - var suffixLength = normalizeDateTimeSuffix && !HasExcludedDateTimeNameComponent(name) - ? GetDateTimeSuffixLength(name) - : 0; - StringBuilder? normalizedName = suffixLength > 0 ? new(name.Length - suffixLength + OnSuffix.Length) : null; + name = normalizeDateTimeSuffix ? name.NormalizeDateTimeSuffix() : name; + StringBuilder? normalizedName = null; int segmentStart = 0; for (int index = 0; index < name.Length - 1; index++) { @@ -77,79 +55,80 @@ public static string NormalizeCSharpAcronyms(this string name, bool normalizeDat return name; } - normalizedName.Append(name, segmentStart, name.Length - suffixLength - segmentStart); - if (suffixLength > 0) - { - normalizedName.Append(name.Length == suffixLength && char.IsLower(name[0]) ? LowercaseOnSuffix : OnSuffix); - } + normalizedName.Append(name, segmentStart, name.Length - segmentStart); return normalizedName.ToString(); } public static string NormalizeDateTimeSuffix(this string name) { - if (HasExcludedDateTimeNameComponent(name)) + if (DateTimeNameRules.HasExcludedComponent(name)) { return name; } - var suffixLength = GetDateTimeSuffixLength(name); + var suffixLength = DateTimeNameRules.GetSuffixLength(name); if (suffixLength == 0) { return name; } var prefix = name[..^suffixLength]; - var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? LowercaseOnSuffix : OnSuffix; + var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) + ? DateTimeNameRules.LowercaseOnSuffix + : DateTimeNameRules.OnSuffix; return prefix + onSuffix; } - private static bool HasExcludedDateTimeNameComponent(string name) + private static class DateTimeNameRules { - var lookup = _dateTimeNameExclusions.GetAlternateLookup>(); - return (name.Length >= FromName.Length && lookup.Contains(name.AsSpan(0, FromName.Length))) || - (name.Length >= ToName.Length && lookup.Contains(name.AsSpan(0, ToName.Length))) || - (name.Length >= PointInTimeName.Length && lookup.Contains(name.AsSpan(^PointInTimeName.Length))); - } - - private static int GetDateTimeSuffixLength(string name) - { - if (name.EndsWith(TimestampSuffix, StringComparison.Ordinal) || - name.EndsWith(TimeStampSuffix, StringComparison.Ordinal)) - { - return TimestampSuffix.Length; - } - - if (name.Equals(TimestampSuffix, StringComparison.OrdinalIgnoreCase)) + private const string AtSuffix = "At"; + private const string DateSuffix = "Date"; + private const string DateTimeSuffix = "DateTime"; + private const string FromName = "From"; + internal const string LowercaseOnSuffix = "on"; + internal const string OnSuffix = "On"; + private const string PointInTimeName = "PointInTime"; + private const string TimeStampSuffix = "TimeStamp"; + private const string TimeSuffix = "Time"; + private const string TimestampSuffix = "Timestamp"; + private const string ToName = "To"; + + internal static bool HasExcludedComponent(string name) { - return TimestampSuffix.Length; + return name.StartsWith(FromName, StringComparison.OrdinalIgnoreCase) || + name.StartsWith(ToName, StringComparison.OrdinalIgnoreCase) || + name.EndsWith(PointInTimeName, StringComparison.OrdinalIgnoreCase); } - if (name.Length > DateTimeSuffix.Length && name.EndsWith(DateTimeSuffix, StringComparison.Ordinal)) + internal static int GetSuffixLength(string name) { - return DateTimeSuffix.Length; - } + if (name.EndsWith(TimestampSuffix, StringComparison.Ordinal) || + name.EndsWith(TimeStampSuffix, StringComparison.Ordinal) || + name.Equals(TimestampSuffix, StringComparison.OrdinalIgnoreCase)) + { + return TimestampSuffix.Length; + } - if (name.Length > TimeSuffix.Length && name.EndsWith(TimeSuffix, StringComparison.Ordinal)) - { - return TimeSuffix.Length; - } + if (name.Length > DateTimeSuffix.Length && name.EndsWith(DateTimeSuffix, StringComparison.Ordinal)) + { + return DateTimeSuffix.Length; + } - if (name.Equals(DateSuffix, StringComparison.OrdinalIgnoreCase)) - { - return DateSuffix.Length; - } + if (name.Length > TimeSuffix.Length && name.EndsWith(TimeSuffix, StringComparison.Ordinal)) + { + return TimeSuffix.Length; + } - if (name.EndsWith(DateSuffix, StringComparison.Ordinal)) - { - return DateSuffix.Length; - } + if (name.Equals(DateSuffix, StringComparison.OrdinalIgnoreCase) || + name.EndsWith(DateSuffix, StringComparison.Ordinal)) + { + return DateSuffix.Length; + } - if (name.Length > AtSuffix.Length && name.EndsWith(AtSuffix, StringComparison.Ordinal)) - { - return AtSuffix.Length; + return name.Length > AtSuffix.Length && name.EndsWith(AtSuffix, StringComparison.Ordinal) + ? AtSuffix.Length + : 0; } - - return 0; } public static bool IsDateTimeInputType(this InputType inputType) => inputType switch diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index c30d1aa2e44..3dfb91b1cf7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -191,16 +191,9 @@ public static void RestorePreviousParameterNames( var inputParameter = parameter.InputParameter; if (inputParameter is not null && - (string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal) || - (inputParameter is InputMethodParameter { IsExactName: false } && - inputParameter.Type.IsDateTimeInputType() && - string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(), StringComparison.Ordinal)))) + string.Equals(inputParameter.Name, inputParameter.OriginalName, StringComparison.Ordinal)) { - var originalName = inputParameter.OriginalName; - if (!string.IsNullOrEmpty(originalName)) - { - preservedName = FindPreviousParameterName(lastContractView, originalName, method.Signature.Name); - } + preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } // Fall back to a positional match for synthesized parameters From b02cbaf6ef6d6f651d2b857c468bd311ce9d0871 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:09:21 +0000 Subject: [PATCH 07/22] fix: refine date naming tests Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Utilities/BackCompatHelper.cs | 3 +- .../test/Providers/ParameterProviderTests.cs | 32 ++++++++++++------- .../test/Providers/PropertyProviderTests.cs | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 3dfb91b1cf7..b08a6bd9368 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,8 +190,7 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null && - string.Equals(inputParameter.Name, inputParameter.OriginalName, StringComparison.Ordinal)) + if (inputParameter is not null) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index e39797743e9..a3fe1cea310 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -103,18 +103,26 @@ private static IEnumerable DateTimeParameterNameTestCases() "TypeSpec.utcDateTime", InputPrimitiveType.String); - yield return new TestCaseData("startTime", dateTime, false, "startOn"); - yield return new TestCaseData("createdAt", dateTime, false, "createdOn"); - yield return new TestCaseData("timestamp", dateTime, false, "on"); - yield return new TestCaseData("date", InputPrimitiveType.PlainDate, false, "on"); - yield return new TestCaseData("modifiedAt", dateTime.WithNullable(true), false, "modifiedOn"); - yield return new TestCaseData("fromTime", dateTime, false, "fromTime"); - yield return new TestCaseData("toDate", dateTime, false, "toDate"); - yield return new TestCaseData("pointInTime", dateTime, false, "pointInTime"); - yield return new TestCaseData("recoveryPointInTime", dateTime, false, "recoveryPointInTime"); - yield return new TestCaseData("startTime", InputPrimitiveType.String, false, "startTime"); - yield return new TestCaseData("creationTimestamp", InputPrimitiveType.String, false, "creationTimestamp"); - yield return new TestCaseData("createdAt", dateTime, true, "createdAt"); + var testCases = new (string Name, InputType Type, string NormalizedName)[] + { + ("startTime", dateTime, "startOn"), + ("createdAt", dateTime, "createdOn"), + ("timestamp", dateTime, "on"), + ("date", InputPrimitiveType.PlainDate, "on"), + ("modifiedAt", dateTime.WithNullable(true), "modifiedOn"), + ("fromTime", dateTime, "fromTime"), + ("toDate", dateTime, "toDate"), + ("pointInTime", dateTime, "pointInTime"), + ("recoveryPointInTime", dateTime, "recoveryPointInTime"), + ("startTime", InputPrimitiveType.String, "startTime"), + ("creationTimestamp", InputPrimitiveType.String, "creationTimestamp") + }; + + foreach (var testCase in testCases) + { + yield return new TestCaseData(testCase.Name, testCase.Type, false, testCase.NormalizedName); + yield return new TestCaseData(testCase.Name, testCase.Type, true, testCase.Name); + } } private static IEnumerable NotEqualsTestCases() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index 1f289017aba..5d755ab0c34 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -263,7 +263,7 @@ private static IEnumerable DateTimePropertyNameTestCases() yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModificationOn"); yield return new TestCaseData("Timestamp", dateTime, false, "On"); yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpirationOn"); - yield return new TestCaseData("RecordedAt", dateTime, false, "RecordedOn"); + yield return new TestCaseData("RecordedAt", InputPrimitiveType.String, false, "RecordedAt"); yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, false, "On"); yield return new TestCaseData("SnapshotTimestamp", dateTime.WithNullable(true), false, "SnapshotOn"); yield return new TestCaseData("FromTime", dateTime, false, "FromTime"); From de464c1e06ca30216d49c598b903100318ffd5ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:42:54 +0000 Subject: [PATCH 08/22] fix: preserve custom back compat parameter names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Utilities/BackCompatHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index b08a6bd9368..6798da935bc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,7 +190,11 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null) + if (inputParameter is not null && + (string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal) || + (inputParameter is InputMethodParameter { IsExactName: false } && + inputParameter.Type.IsDateTimeInputType() && + string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(), StringComparison.Ordinal)))) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } From f177ea1702981745de0b39fa756304c0be64c03c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:37:10 +0000 Subject: [PATCH 09/22] fix: generalize back compat parameter name restoration Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Utilities/BackCompatHelper.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 6798da935bc..b08a6bd9368 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,11 +190,7 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null && - (string.Equals(parameter.Name, inputParameter.Name, StringComparison.Ordinal) || - (inputParameter is InputMethodParameter { IsExactName: false } && - inputParameter.Type.IsDateTimeInputType() && - string.Equals(parameter.Name, inputParameter.Name.NormalizeDateTimeSuffix(), StringComparison.Ordinal)))) + if (inputParameter is not null) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } From 862f3672d73e9500d7ad151428ef29c897806f0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 20:17:11 +0000 Subject: [PATCH 10/22] fix: preserve transformed back compat parameter names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ParameterProvider.cs | 4 ++++ .../src/Utilities/BackCompatHelper.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 496612af7d3..9021b36c283 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -56,6 +56,8 @@ public sealed class ParameterProvider : IEquatable /// public InputParameter? InputParameter { get; private set; } + internal bool NameIsInitializedFromInputParameter { get; private set; } + /// /// Creates a from an . /// @@ -67,6 +69,7 @@ public ParameterProvider(InputParameter inputParameter) && inputParameter.Type.IsDateTimeInputType() ? inputParameter.Name.NormalizeDateTimeSuffix() : inputParameter.Name; + NameIsInitializedFromInputParameter = true; Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type) ?? throw new InvalidOperationException($"Failed to create CSharpType for {inputParameter.Type}"); if (!inputParameter.IsRequired) @@ -310,6 +313,7 @@ public void Update( { if (name is not null) { + NameIsInitializedFromInputParameter = false; Name = name; _asVariable?.Update(name: name); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index b08a6bd9368..896267ed2ef 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,7 +190,7 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null) + if (inputParameter is not null && parameter.NameIsInitializedFromInputParameter) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } From 06f7bd6566a3c267eab6e111a4e0e0920d500ddf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:05:25 +0000 Subject: [PATCH 11/22] fix: simplify parameter name preservation Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Primitives/ScmKnownParameters.cs | 5 ++--- .../src/Providers/RestClientProvider.cs | 4 ++-- .../src/Providers/ScmMethodProviderCollection.cs | 2 +- .../src/Providers/ParameterProvider.cs | 4 ---- .../src/Utilities/BackCompatHelper.cs | 2 +- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index a9125f412a2..c222c9d63f4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -41,13 +41,12 @@ public static ParameterProvider ClientOptions(CSharpType clientOptionsType) private static readonly FormattableString RequestContentDescription = $"The content to send as the body of the request."; private const string RequestContentParameterName = "content"; - public static ParameterProvider CreateRequestContent(InputParameter? parameter = null, bool optional = false, bool nullable = false) => new( + public static ParameterProvider CreateRequestContent(bool optional = false, bool nullable = false) => new( RequestContentParameterName, RequestContentDescription, ScmCodeModelGenerator.Instance.TypeFactory.RequestContentApi.RequestContentType, location: ParameterLocation.Body, - defaultValue: optional ? Null : null, - inputParameter: parameter) + defaultValue: optional ? Null : null) { Validation = nullable ? ParameterValidationType.None : ParameterValidationType.AssertNotNull, }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 747e51bcc2f..77c12bf3418 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -1316,11 +1316,11 @@ internal static List GetMethodParameters( { if (methodType == ScmMethodKind.CreateRequest) { - parameter = ScmKnownParameters.CreateRequestContent(inputParam); + parameter = ScmKnownParameters.CreateRequestContent(); } else { - parameter = ScmKnownParameters.CreateRequestContent(inputParam, + parameter = ScmKnownParameters.CreateRequestContent( optional: parameter.DefaultValue != null); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index fb0bb6e6025..a9095402ae9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -1509,7 +1509,7 @@ private ParameterProvider ProcessOptionalParameters( if (optionalParameter.IsContentParameter) { var nullableRequiredContent = - ScmKnownParameters.CreateRequestContent(optionalParameter.InputParameter, nullable: true); + ScmKnownParameters.CreateRequestContent(nullable: true); requiredParameters.Add(nullableRequiredContent); // Update the body param in the underlying collection var bodyParamIndex = ProtocolMethodParameters.IndexOf(optionalParameter); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 9021b36c283..496612af7d3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -56,8 +56,6 @@ public sealed class ParameterProvider : IEquatable /// public InputParameter? InputParameter { get; private set; } - internal bool NameIsInitializedFromInputParameter { get; private set; } - /// /// Creates a from an . /// @@ -69,7 +67,6 @@ public ParameterProvider(InputParameter inputParameter) && inputParameter.Type.IsDateTimeInputType() ? inputParameter.Name.NormalizeDateTimeSuffix() : inputParameter.Name; - NameIsInitializedFromInputParameter = true; Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type) ?? throw new InvalidOperationException($"Failed to create CSharpType for {inputParameter.Type}"); if (!inputParameter.IsRequired) @@ -313,7 +310,6 @@ public void Update( { if (name is not null) { - NameIsInitializedFromInputParameter = false; Name = name; _asVariable?.Update(name: name); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 896267ed2ef..b08a6bd9368 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,7 +190,7 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null && parameter.NameIsInitializedFromInputParameter) + if (inputParameter is not null) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } From 8f36465bd76814c0211ba76322f723bad56bea2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:47:09 +0000 Subject: [PATCH 12/22] test: preserve XML date normalization assertion Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../XmlDeserializationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index d0ae40f8f84..52666216903 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -261,8 +261,8 @@ public void XmlDeserializationHandlesDateTimeOffsetProperty() Assert.IsNotNull(xmlDeserializationMethod); var methodBody = xmlDeserializationMethod!.BodyStatements!.ToDisplayString(); - Assert.IsTrue(methodBody.Contains("GetDateTimeOffset(\"O\")"), - $"DateTimeOffset property should use RFC3339 format. Actual:\n{methodBody}"); + Assert.IsTrue(methodBody.Contains("@on = child.GetDateTimeOffset(\"O\")"), + $"DateTimeOffset property should use child.GetDateTimeOffset(\"O\") with RFC3339 format. Actual:\n{methodBody}"); } [Test] From 87a9e437989e496b514a2a8f91b8617f0fd097af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:24:19 +0000 Subject: [PATCH 13/22] fix: retain request content input metadata Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Primitives/ScmKnownParameters.cs | 5 +++-- .../src/Providers/RestClientProvider.cs | 4 ++-- .../src/Providers/ScmMethodProviderCollection.cs | 2 +- .../test/ScmKnownParametersTests.cs | 11 +++++++++++ .../src/Utilities/BackCompatHelper.cs | 5 +++-- ...estoresSynthesizedParameterNameBySignatureMatch.cs | 6 +++--- .../TestClient.cs | 3 ++- .../test/Providers/TypeProviderTests.cs | 9 +++++---- 8 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index c222c9d63f4..a9125f412a2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -41,12 +41,13 @@ public static ParameterProvider ClientOptions(CSharpType clientOptionsType) private static readonly FormattableString RequestContentDescription = $"The content to send as the body of the request."; private const string RequestContentParameterName = "content"; - public static ParameterProvider CreateRequestContent(bool optional = false, bool nullable = false) => new( + public static ParameterProvider CreateRequestContent(InputParameter? parameter = null, bool optional = false, bool nullable = false) => new( RequestContentParameterName, RequestContentDescription, ScmCodeModelGenerator.Instance.TypeFactory.RequestContentApi.RequestContentType, location: ParameterLocation.Body, - defaultValue: optional ? Null : null) + defaultValue: optional ? Null : null, + inputParameter: parameter) { Validation = nullable ? ParameterValidationType.None : ParameterValidationType.AssertNotNull, }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 77c12bf3418..747e51bcc2f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -1316,11 +1316,11 @@ internal static List GetMethodParameters( { if (methodType == ScmMethodKind.CreateRequest) { - parameter = ScmKnownParameters.CreateRequestContent(); + parameter = ScmKnownParameters.CreateRequestContent(inputParam); } else { - parameter = ScmKnownParameters.CreateRequestContent( + parameter = ScmKnownParameters.CreateRequestContent(inputParam, optional: parameter.DefaultValue != null); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index a9095402ae9..fb0bb6e6025 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -1509,7 +1509,7 @@ private ParameterProvider ProcessOptionalParameters( if (optionalParameter.IsContentParameter) { var nullableRequiredContent = - ScmKnownParameters.CreateRequestContent(nullable: true); + ScmKnownParameters.CreateRequestContent(optionalParameter.InputParameter, nullable: true); requiredParameters.Add(nullableRequiredContent); // Update the body param in the underlying collection var bodyParamIndex = ProtocolMethodParameters.IndexOf(optionalParameter); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs index 40e7c9e92bd..dc9de44ad36 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs @@ -3,7 +3,9 @@ using System; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -25,6 +27,15 @@ public void BinaryDataParameterHasValidation() Assert.AreEqual(ParameterValidationType.AssertNotNull, parameter.Validation); } + [Test] + public void BinaryDataParameterRetainsSourceInput() + { + var inputParameter = InputFactory.BodyParameter("body", InputPrimitiveType.String); + var parameter = ScmKnownParameters.CreateRequestContent(inputParameter); + + Assert.AreSame(inputParameter, parameter.InputParameter); + } + [Test] public void RepeatabilityRequestIdParamHasDefaultValue() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index b08a6bd9368..023c58caaa1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -190,12 +190,13 @@ public static void RestorePreviousParameterNames( string? preservedName = null; var inputParameter = parameter.InputParameter; - if (inputParameter is not null) + if (inputParameter is not null && !parameter.IsContentParameter) { preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } - // Fall back to a positional match for synthesized parameters + // Fall back to a positional match for synthesized parameters, including content parameters + // whose input metadata describes the body parameter rather than the public parameter. if (string.IsNullOrEmpty(preservedName)) { preservedName = matchingPrevious?.Signature.Parameters[i].Name; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs index 23272d7bc0b..238aaccf57d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs @@ -6,10 +6,10 @@ namespace Test { public partial class TestClient { - public string Foo(string defaultName) + public string Foo(string content) { - this.Validate(defaultName); - return defaultName; + this.Validate(content); + return content; } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs index 4b06d37c36d..bd7390f4414 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs @@ -2,6 +2,7 @@ namespace Test { public class TestClient { - public string Foo(string defaultName) { return null; } + public string Foo(string content) { return null; } + public string Foo(int param1) { return null; } } } \ No newline at end of file diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index 303f8157a8a..a1ec5c6f935 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -1021,18 +1021,19 @@ public async Task BuildMethodsForBackCompatibilityRestoresRenamedParameterBySign Assert.AreEqual(Helpers.GetExpectedFromFile(), actual); } - // Validates that synthesized parameters retain the positional fallback even when their public name - // differs from the InputParameter they originated from. + // Validates that synthesized content parameters retain the positional fallback even when their + // source input name belongs to another overload in the last contract. [Test] public async Task BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch() { await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - var inputParameter = InputFactory.QueryParameter("wireName", InputPrimitiveType.String, isRequired: true); + var inputParameter = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true); var parameter = new ParameterProvider( - "default", + "content", $"", new CSharpType(typeof(string)), + location: ParameterLocation.Body, inputParameter: inputParameter); var fooMethod = new MethodProvider( new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, new CSharpType(typeof(string)), $"", [parameter]), From 0654a31f4e88fe7cfbc1a8e081e0e469b4c0afdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:50:13 +0000 Subject: [PATCH 14/22] test: cover date name backcompat constructors Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelFactoryProviderTests.cs | 25 +++++++++++ .../SampleNamespaceModelFactory.cs | 19 ++++++++ .../ModelProviders/ModelProviderTests.cs | 44 +++++++++++++++++++ .../DateTimeModel.cs | 11 +++++ 4 files changed, 99 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ModelFactoryParameterPreservesDateTimeSuffix/SampleNamespaceModelFactory.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix/DateTimeModel.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index 3fd14bf42b4..58ec9769bcd 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -621,6 +621,31 @@ public async Task BackCompatibility_MultipleParamNamesChanged() Assert.AreEqual("previousDictProp", docParams[3].Parameter.Name); } + [Test] + public async Task BackCompatibility_ModelFactoryParameterPreservesDateTimeSuffix() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var model = InputFactory.Model( + "DateTimeModel", + properties: [InputFactory.Property("StartTime", dateTime, isRequired: true)]); + + _instance = (await MockHelpers.LoadMockGeneratorAsync( + inputNamespaceName: "Sample.Namespace", + inputModelTypes: [model], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync())).Object; + + var modelFactory = _instance.OutputLibrary.ModelFactory.Value; + modelFactory.ProcessTypeForBackCompatibility(); + + var method = modelFactory.Methods.Single(m => m.Signature.Name == "DateTimeModel"); + Assert.AreEqual("startTime", method.Signature.Parameters.Single().Name); + StringAssert.Contains("DateTimeModel(startTime", method.BodyStatements!.ToDisplayString()); + } + // Validates that when a new property is added AND the previous contract used different // names for some of the surviving parameters, the rename-only fast path does NOT apply // (parameter counts differ). Instead the standard "new property added" backcompat overload diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ModelFactoryParameterPreservesDateTimeSuffix/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ModelFactoryParameterPreservesDateTimeSuffix/SampleNamespaceModelFactory.cs new file mode 100644 index 00000000000..9dfd511ce20 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ModelFactoryParameterPreservesDateTimeSuffix/SampleNamespaceModelFactory.cs @@ -0,0 +1,19 @@ +using System; + +namespace Sample.Namespace +{ + public static partial class SampleNamespaceModelFactory + { + public static DateTimeModel DateTimeModel(DateTimeOffset startTime = default) + { + return default; + } + } +} + +namespace Sample.Models +{ + public partial class DateTimeModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs index 3cc17f7d751..2cee3207f40 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs @@ -2748,6 +2748,50 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + [Test] + public void ConstructorParameterNormalizesDateTimeSuffix() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var inputModel = InputFactory.Model( + "DateTimeModel", + usage: InputModelTypeUsage.Input, + properties: [InputFactory.Property("StartTime", dateTime, isRequired: true)]); + + MockHelpers.LoadMockGenerator(inputModelTypes: [inputModel]); + + var modelProvider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType().Single(); + var constructor = modelProvider.Constructors.Single(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.AreEqual("startOn", constructor.Signature.Parameters.Single().Name); + } + + [Test] + public async Task BackCompat_ConstructorParameterPreservesDateTimeSuffix() + { + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var inputModel = InputFactory.Model( + "DateTimeModel", + usage: InputModelTypeUsage.Input, + properties: [InputFactory.Property("StartTime", dateTime, isRequired: true)]); + + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType().Single(); + modelProvider.ProcessTypeForBackCompatibility(); + + var constructor = modelProvider.Constructors.Single(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.AreEqual("startTime", constructor.Signature.Parameters.Single().Name); + } + [Test] public async Task BackCompat_ConstructorNotRestoredWhenLastContractMissing() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix/DateTimeModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix/DateTimeModel.cs new file mode 100644 index 00000000000..7ce9bb2696e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix/DateTimeModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace Sample.Models +{ + public partial class DateTimeModel + { + public DateTimeModel(DateTimeOffset startTime) + { + } + } +} From 1a5e99d0b10f72607d90c9f1315378eb95835682 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:14:40 +0000 Subject: [PATCH 15/22] test: restore back compat test fixtures Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/ScmKnownParametersTests.cs | 11 ----------- .../src/Utilities/BackCompatHelper.cs | 3 +-- ...estoresSynthesizedParameterNameBySignatureMatch.cs | 6 +++--- .../TestClient.cs | 3 +-- .../test/Providers/TypeProviderTests.cs | 9 ++++----- 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs index dc9de44ad36..40e7c9e92bd 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ScmKnownParametersTests.cs @@ -3,9 +3,7 @@ using System; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; -using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; -using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -27,15 +25,6 @@ public void BinaryDataParameterHasValidation() Assert.AreEqual(ParameterValidationType.AssertNotNull, parameter.Validation); } - [Test] - public void BinaryDataParameterRetainsSourceInput() - { - var inputParameter = InputFactory.BodyParameter("body", InputPrimitiveType.String); - var parameter = ScmKnownParameters.CreateRequestContent(inputParameter); - - Assert.AreSame(inputParameter, parameter.InputParameter); - } - [Test] public void RepeatabilityRequestIdParamHasDefaultValue() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 023c58caaa1..2cf9a28e3cc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -195,8 +195,7 @@ public static void RestorePreviousParameterNames( preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); } - // Fall back to a positional match for synthesized parameters, including content parameters - // whose input metadata describes the body parameter rather than the public parameter. + // Fall back to a positional match for synthesized parameters if (string.IsNullOrEmpty(preservedName)) { preservedName = matchingPrevious?.Signature.Parameters[i].Name; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs index 238aaccf57d..23272d7bc0b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch.cs @@ -6,10 +6,10 @@ namespace Test { public partial class TestClient { - public string Foo(string content) + public string Foo(string defaultName) { - this.Validate(content); - return content; + this.Validate(defaultName); + return defaultName; } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs index bd7390f4414..4b06d37c36d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch/TestClient.cs @@ -2,7 +2,6 @@ namespace Test { public class TestClient { - public string Foo(string content) { return null; } - public string Foo(int param1) { return null; } + public string Foo(string defaultName) { return null; } } } \ No newline at end of file diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index a1ec5c6f935..303f8157a8a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -1021,19 +1021,18 @@ public async Task BuildMethodsForBackCompatibilityRestoresRenamedParameterBySign Assert.AreEqual(Helpers.GetExpectedFromFile(), actual); } - // Validates that synthesized content parameters retain the positional fallback even when their - // source input name belongs to another overload in the last contract. + // Validates that synthesized parameters retain the positional fallback even when their public name + // differs from the InputParameter they originated from. [Test] public async Task BuildMethodsForBackCompatibilityRestoresSynthesizedParameterNameBySignatureMatch() { await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - var inputParameter = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true); + var inputParameter = InputFactory.QueryParameter("wireName", InputPrimitiveType.String, isRequired: true); var parameter = new ParameterProvider( - "content", + "default", $"", new CSharpType(typeof(string)), - location: ParameterLocation.Body, inputParameter: inputParameter); var fooMethod = new MethodProvider( new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, new CSharpType(typeof(string)), $"", [parameter]), From 5c1aace5cfc861ce8bd4ed986ebb292b9452caad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:37:54 +0000 Subject: [PATCH 16/22] test: validate date suffix ctor tests with TestData Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelProviders/ModelProviderTests.cs | 8 ++++++ ...tructorParameterPreservesDateTimeSuffix.cs | 27 +++++++++++++++++++ ...ructorParameterNormalizesDateTimeSuffix.cs | 27 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/ConstructorParameterNormalizesDateTimeSuffix.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs index 2cee3207f40..7979b79b64d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs @@ -2766,6 +2766,10 @@ public void ConstructorParameterNormalizesDateTimeSuffix() var modelProvider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType().Single(); var constructor = modelProvider.Constructors.Single(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); Assert.AreEqual("startOn", constructor.Signature.Parameters.Single().Name); + + var writer = new TypeProviderWriter(modelProvider); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } [Test] @@ -2790,6 +2794,10 @@ await MockHelpers.LoadMockGeneratorAsync( var constructor = modelProvider.Constructors.Single(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); Assert.AreEqual("startTime", constructor.Signature.Parameters.Single().Name); + + var writer = new TypeProviderWriter(modelProvider); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } [Test] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs new file mode 100644 index 00000000000..afe7711f25f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System; +using System.Collections.Generic; + +namespace Sample.Models +{ + public partial class DateTimeModel + { + private protected readonly global::System.Collections.Generic.IDictionary _additionalBinaryDataProperties; + + public DateTimeModel(global::System.DateTimeOffset startTime) + { + StartOn = startTime; + } + + internal DateTimeModel(global::System.DateTimeOffset startTime, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + { + StartOn = startTime; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + public global::System.DateTimeOffset StartOn { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/ConstructorParameterNormalizesDateTimeSuffix.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/ConstructorParameterNormalizesDateTimeSuffix.cs new file mode 100644 index 00000000000..223d9b6a5b5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/ConstructorParameterNormalizesDateTimeSuffix.cs @@ -0,0 +1,27 @@ +// + +#nullable disable + +using System; +using System.Collections.Generic; + +namespace Sample.Models +{ + public partial class DateTimeModel + { + private protected readonly global::System.Collections.Generic.IDictionary _additionalBinaryDataProperties; + + public DateTimeModel(global::System.DateTimeOffset startOn) + { + StartOn = startOn; + } + + internal DateTimeModel(global::System.DateTimeOffset startOn, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + { + StartOn = startOn; + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + public global::System.DateTimeOffset StartOn { get; } + } +} From 28d237379f4388e71410b059f421a99a66b3e740 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:28:45 +0000 Subject: [PATCH 17/22] fix: normalize date suffix for all input parameters Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ScmMethodProviderCollectionTests.cs | 45 +++++++++++++++++++ ...ceMethodForwardsNormalizedDateParameter.cs | 1 + .../src/Providers/ParameterProvider.cs | 3 +- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 92a5c820965..b713cc72453 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -611,6 +611,51 @@ public async Task SpreadModelWithOptionalDictionaryIsNotNull() Assert.AreEqual(Helpers.GetExpectedFromFile(), methodBody); } + [Test] + public async Task ConvenienceMethodForwardsNormalizedDateParameter() + { + var dateType = new InputDateTimeType( + DateTimeKnownEncoding.Rfc7231, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var operation = InputFactory.Operation( + "GetThing", + parameters: [InputFactory.HeaderParameter("requestDate", dateType, isRequired: true)], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "GetThing", + operation, + parameters: + [ + InputFactory.MethodParameter( + "requestDate", + dateType, + isRequired: true, + location: InputRequestLocation.Header) + ]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync(clients: () => [inputClient]); + + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient); + Assert.IsNotNull(client); + var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!); + + // Both the protocol and convenience methods use the normalized name so that the + // convenience method still forwards the value to the protocol method. + foreach (var method in methodCollection) + { + Assert.IsTrue(method.Signature.Parameters.Any(p => p.Name == "requestOn")); + } + + var asyncConvenienceMethod = methodCollection.Single(m => + m.Signature.Name.EndsWith("Async") + && m.Signature.Parameters.Any(p => p.Type.Equals(typeof(CancellationToken)))); + Assert.AreEqual( + Helpers.GetExpectedFromFile(), + asyncConvenienceMethod.BodyStatements!.ToDisplayString()); + } + [Test] public void ListMethodWithNoPaging() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs new file mode 100644 index 00000000000..ed8766c2128 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs @@ -0,0 +1 @@ +return await this.GetThingAsync(requestOn, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 496612af7d3..60be833f9d5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -63,8 +63,7 @@ public sealed class ParameterProvider : IEquatable public ParameterProvider(InputParameter inputParameter) { InputParameter = inputParameter; - Name = inputParameter is InputMethodParameter && !inputParameter.IsExactName - && inputParameter.Type.IsDateTimeInputType() + Name = !inputParameter.IsExactName && inputParameter.Type.IsDateTimeInputType() ? inputParameter.Name.NormalizeDateTimeSuffix() : inputParameter.Name; Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; From e46c8f6cdf5afcd5391155f58bc2463717e85472 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:34:24 +0000 Subject: [PATCH 18/22] test: validate convenience method signature in TestData Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ScmMethodProviderCollectionTests.cs | 4 +++- .../ConvenienceMethodForwardsNormalizedDateParameter.cs | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index b713cc72453..6aadbf6d2f5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -651,9 +651,11 @@ public async Task ConvenienceMethodForwardsNormalizedDateParameter() var asyncConvenienceMethod = methodCollection.Single(m => m.Signature.Name.EndsWith("Async") && m.Signature.Parameters.Any(p => p.Type.Equals(typeof(CancellationToken)))); + using var writer = new CodeWriter(); + writer.WriteMethod(asyncConvenienceMethod); Assert.AreEqual( Helpers.GetExpectedFromFile(), - asyncConvenienceMethod.BodyStatements!.ToDisplayString()); + writer.ToString(false)); } [Test] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs index ed8766c2128..0c3cd5e4f00 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/ConvenienceMethodForwardsNormalizedDateParameter.cs @@ -1 +1,4 @@ -return await this.GetThingAsync(requestOn, cancellationToken.ToRequestOptions()).ConfigureAwait(false); +public virtual async global::System.Threading.Tasks.Task GetThingAsync(global::System.DateTimeOffset requestOn, global::System.Threading.CancellationToken cancellationToken = default) +{ + return await this.GetThingAsync(requestOn, cancellationToken.ToRequestOptions()).ConfigureAwait(false); +} From 3d558b15871509e0a6986f41c89d8024e8c4f8f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:45:29 +0000 Subject: [PATCH 19/22] Add back-compat regression test for date parameter forwarding in convenience calls Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviders/ClientProviderTests.cs | 51 +++++++++++++++++++ ...rameterNameIsPreservedInConvenienceCall.cs | 9 ++++ .../TestClient.cs | 20 ++++++++ 3 files changed, 80 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index 2fd9e420c04..b8d6e04e21e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -2805,6 +2805,57 @@ private static void AssertHasFields(TypeProvider provider, IReadOnlyList requestOn) on both the protocol and the + // convenience surface, so the previously published name is restored consistently for both and the + // convenience method forwards every argument positionally. When only one surface is normalized, the + // date argument is dropped (passed as null) and the remaining arguments are passed by name. + [Test] + public async Task BackCompatibility_DateParameterNameIsPreservedInConvenienceCall() + { + var dateType = new InputDateTimeType( + DateTimeKnownEncoding.Rfc7231, + "utcDateTime", + "TypeSpec.utcDateTime", + InputPrimitiveType.String); + var operation = InputFactory.Operation( + "TestMethod", + parameters: + [ + InputFactory.HeaderParameter("requestDate", dateType), + InputFactory.HeaderParameter("ifMatch", InputPrimitiveType.String) + ]); + var method = InputFactory.BasicServiceMethod( + "TestMethod", + operation, + parameters: + [ + InputFactory.MethodParameter("requestDate", dateType, location: InputRequestLocation.Header), + InputFactory.MethodParameter("ifMatch", InputPrimitiveType.String, location: InputRequestLocation.Header) + ]); + var client = InputFactory.Client(TestClientName, methods: [method]); + + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [client], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType().FirstOrDefault(); + Assert.IsNotNull(clientProvider); + Assert.IsNotNull(clientProvider!.LastContractView); + + clientProvider!.ProcessTypeForBackCompatibility(); + + var protocolMethod = clientProvider.Methods + .Single(m => m.Signature.Name == "TestMethod" && m is ScmMethodProvider { Kind: ScmMethodKind.Protocol }); + var convenienceMethod = clientProvider.Methods + .Single(m => m.Signature.Name == "TestMethod" && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience }); + + using var writer = new CodeWriter(); + writer.WriteMethod(protocolMethod); + writer.WriteMethod(convenienceMethod); + + Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.ToString(false)); + } + [Test] public async Task BackCompatibility_ProtocolMethodParamOrderChanged() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs new file mode 100644 index 00000000000..6f3458c743e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs @@ -0,0 +1,9 @@ +public virtual global::System.ClientModel.ClientResult TestMethod(global::System.DateTimeOffset? requestDate, string ifMatch, global::System.ClientModel.Primitives.RequestOptions options) +{ + using global::System.ClientModel.Primitives.PipelineMessage message = this.CreateTestMethodRequest(requestDate, ifMatch, options); + return global::System.ClientModel.ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); +} +public virtual global::System.ClientModel.ClientResult TestMethod(global::System.DateTimeOffset? requestDate = default, string ifMatch = default, global::System.Threading.CancellationToken cancellationToken = default) +{ + return this.TestMethod(requestDate, ifMatch, cancellationToken.ToRequestOptions()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall/TestClient.cs new file mode 100644 index 00000000000..b885949e3bc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall/TestClient.cs @@ -0,0 +1,20 @@ +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult TestMethod(DateTimeOffset? requestDate, string ifMatch, RequestOptions options) + { + throw new NotImplementedException(); + } + + public virtual Task TestMethodAsync(DateTimeOffset? requestDate, string ifMatch, RequestOptions options) + { + throw new NotImplementedException(); + } + } +} From 02be6dfd4f43413becd794d5b7ab1b6b964a9bae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:48:01 +0000 Subject: [PATCH 20/22] Validate async overloads in date parameter back-compat regression test Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviders/ClientProviderTests.cs | 14 +++++++------- ...ateParameterNameIsPreservedInConvenienceCall.cs | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index b8d6e04e21e..5e6bf4c5249 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -2844,14 +2844,14 @@ public async Task BackCompatibility_DateParameterNameIsPreservedInConvenienceCal clientProvider!.ProcessTypeForBackCompatibility(); - var protocolMethod = clientProvider.Methods - .Single(m => m.Signature.Name == "TestMethod" && m is ScmMethodProvider { Kind: ScmMethodKind.Protocol }); - var convenienceMethod = clientProvider.Methods - .Single(m => m.Signature.Name == "TestMethod" && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience }); - using var writer = new CodeWriter(); - writer.WriteMethod(protocolMethod); - writer.WriteMethod(convenienceMethod); + foreach (var methodName in new[] { "TestMethod", "TestMethodAsync" }) + { + writer.WriteMethod(clientProvider.Methods + .Single(m => m.Signature.Name == methodName && m is ScmMethodProvider { Kind: ScmMethodKind.Protocol })); + writer.WriteMethod(clientProvider.Methods + .Single(m => m.Signature.Name == methodName && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience })); + } Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.ToString(false)); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs index 6f3458c743e..a20285b016e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/BackCompatibility_DateParameterNameIsPreservedInConvenienceCall.cs @@ -7,3 +7,12 @@ { return this.TestMethod(requestDate, ifMatch, cancellationToken.ToRequestOptions()); } +public virtual async global::System.Threading.Tasks.Task TestMethodAsync(global::System.DateTimeOffset? requestDate, string ifMatch, global::System.ClientModel.Primitives.RequestOptions options) +{ + using global::System.ClientModel.Primitives.PipelineMessage message = this.CreateTestMethodRequest(requestDate, ifMatch, options); + return global::System.ClientModel.ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); +} +public virtual async global::System.Threading.Tasks.Task TestMethodAsync(global::System.DateTimeOffset? requestDate = default, string ifMatch = default, global::System.Threading.CancellationToken cancellationToken = default) +{ + return await this.TestMethodAsync(requestDate, ifMatch, cancellationToken.ToRequestOptions()).ConfigureAwait(false); +} From 05e8a3f9d70c8bfc14640cf99953c2f6b0fb07bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 20:18:05 +0000 Subject: [PATCH 21/22] fix: map noun prefixes to verb forms in date name normalization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Shared/CSharpNameExtensions.cs | 24 ++++++++++++++++++- .../test/Providers/ParameterProviderTests.cs | 5 ++++ .../test/Providers/PropertyProviderTests.cs | 9 ++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index f1896e15e89..50bb621d539 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.TypeSpec.Generator.Input; @@ -72,7 +73,7 @@ public static string NormalizeDateTimeSuffix(this string name) return name; } - var prefix = name[..^suffixLength]; + var prefix = DateTimeNameRules.ToVerbForm(name[..^suffixLength]); var onSuffix = prefix.Length == 0 && char.IsLower(name[0]) ? DateTimeNameRules.LowercaseOnSuffix : DateTimeNameRules.OnSuffix; @@ -93,6 +94,27 @@ private static class DateTimeNameRules private const string TimestampSuffix = "Timestamp"; private const string ToName = "To"; + // Nouns that read better as verbs when combined with the "On" suffix, e.g. "ExpirationDate" -> "ExpireOn". + private static readonly Dictionary _nounToVerbMap = new(StringComparer.OrdinalIgnoreCase) + { + ["Creation"] = "Created", + ["Deletion"] = "Deleted", + ["Expiration"] = "Expire", + ["Modification"] = "Modified" + }; + + internal static string ToVerbForm(string prefix) + { + if (!_nounToVerbMap.TryGetValue(prefix, out var verb)) + { + return prefix; + } + + return char.IsLower(prefix[0]) + ? char.ToLowerInvariant(verb[0]) + verb[1..] + : verb; + } + internal static bool HasExcludedComponent(string name) { return name.StartsWith(FromName, StringComparison.OrdinalIgnoreCase) || diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index e372e590572..6bd742f510a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -120,6 +120,11 @@ private static IEnumerable DateTimeParameterNameTestCases() ("timestamp", dateTime, "on"), ("date", InputPrimitiveType.PlainDate, "on"), ("modifiedAt", dateTime.WithNullable(true), "modifiedOn"), + ("expirationDate", dateTime, "expireOn"), + ("creationDate", dateTime, "createdOn"), + ("deletionDateTime", dateTime, "deletedOn"), + ("modificationTime", dateTime, "modifiedOn"), + ("accountExpirationDate", dateTime, "accountExpirationOn"), ("fromTime", dateTime, "fromTime"), ("toDate", dateTime, "toDate"), ("pointInTime", dateTime, "pointInTime"), diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index 67e07632b73..4e8c506d2a5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -282,10 +282,13 @@ private static IEnumerable DateTimePropertyNameTestCases() yield return new TestCaseData("StartTime", dateTime, false, "StartOn"); yield return new TestCaseData("CreatedAt", dateTime, false, "CreatedOn"); - yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletionOn"); - yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModificationOn"); + yield return new TestCaseData("DeletionTimestamp", dateTime, false, "DeletedOn"); + yield return new TestCaseData("ModificationTimeStamp", dateTime, false, "ModifiedOn"); yield return new TestCaseData("Timestamp", dateTime, false, "On"); - yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpirationOn"); + yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpireOn"); + yield return new TestCaseData("CreationDate", dateTime, false, "CreatedOn"); + yield return new TestCaseData("DeletionDateTime", dateTime, false, "DeletedOn"); + yield return new TestCaseData("AccountExpirationDate", dateTime, false, "AccountExpirationOn"); yield return new TestCaseData("RecordedAt", InputPrimitiveType.String, false, "RecordedAt"); yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, false, "On"); yield return new TestCaseData("SnapshotTimestamp", dateTime.WithNullable(true), false, "SnapshotOn"); From f1aaabc0e96af8d57571018711d4ced86ce41c8d Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Thu, 20 Aug 2026 12:34:37 -0500 Subject: [PATCH 22/22] fixes --- .../src/Shared/CSharpNameExtensions.cs | 10 +++++++-- .../test/Providers/ParameterProviderTests.cs | 7 ++++++ .../test/Providers/PropertyProviderTests.cs | 6 +++++ .../Utilities/CSharpNameExtensionsTests.cs | 22 +++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index 50bb621d539..8d56efd8a5e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -89,12 +89,14 @@ private static class DateTimeNameRules internal const string LowercaseOnSuffix = "on"; internal const string OnSuffix = "On"; private const string PointInTimeName = "PointInTime"; + private const string StatusTimeStampName = "StatusTimeStamp"; + private const string StatusTimestampName = "StatusTimestamp"; private const string TimeStampSuffix = "TimeStamp"; private const string TimeSuffix = "Time"; private const string TimestampSuffix = "Timestamp"; private const string ToName = "To"; - // Nouns that read better as verbs when combined with the "On" suffix, e.g. "ExpirationDate" -> "ExpireOn". + // Complete prefixes that read better as verbs when combined with the "On" suffix. private static readonly Dictionary _nounToVerbMap = new(StringComparer.OrdinalIgnoreCase) { ["Creation"] = "Created", @@ -117,9 +119,13 @@ internal static string ToVerbForm(string prefix) internal static bool HasExcludedComponent(string name) { + // StatusTimestamp is a semantic compound. Keep the exclusion exact so names such as + // LastSyncTimestamp continue to normalize. return name.StartsWith(FromName, StringComparison.OrdinalIgnoreCase) || name.StartsWith(ToName, StringComparison.OrdinalIgnoreCase) || - name.EndsWith(PointInTimeName, StringComparison.OrdinalIgnoreCase); + name.EndsWith(PointInTimeName, StringComparison.OrdinalIgnoreCase) || + name.Equals(StatusTimestampName, StringComparison.OrdinalIgnoreCase) || + name.Equals(StatusTimeStampName, StringComparison.OrdinalIgnoreCase); } internal static int GetSuffixLength(string name) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 6bd742f510a..0646f57b13b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -122,9 +122,16 @@ private static IEnumerable DateTimeParameterNameTestCases() ("modifiedAt", dateTime.WithNullable(true), "modifiedOn"), ("expirationDate", dateTime, "expireOn"), ("creationDate", dateTime, "createdOn"), + ("creationTime", dateTime, "createdOn"), + ("expirationDateTime", dateTime.WithNullable(true), "expireOn"), ("deletionDateTime", dateTime, "deletedOn"), ("modificationTime", dateTime, "modifiedOn"), ("accountExpirationDate", dateTime, "accountExpirationOn"), + ("statusTimestamp", dateTime.WithNullable(true), "statusTimestamp"), + ("lastSyncTimestamp", dateTime, "lastSyncOn"), + ("totalTime", dateTime, "totalTime"), + ("topicTimestamp", dateTime.WithNullable(true), "topicTimestamp"), + ("tokenExpirationDate", dateTime, "tokenExpirationDate"), ("fromTime", dateTime, "fromTime"), ("toDate", dateTime, "toDate"), ("pointInTime", dateTime, "pointInTime"), diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs index 4e8c506d2a5..379b15849bf 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/PropertyProviderTests.cs @@ -287,11 +287,17 @@ private static IEnumerable DateTimePropertyNameTestCases() yield return new TestCaseData("Timestamp", dateTime, false, "On"); yield return new TestCaseData("ExpirationDate", dateTime, false, "ExpireOn"); yield return new TestCaseData("CreationDate", dateTime, false, "CreatedOn"); + yield return new TestCaseData("CreationTime", dateTime, false, "CreatedOn"); + yield return new TestCaseData("ExpirationDateTime", dateTime.WithNullable(true), false, "ExpireOn"); yield return new TestCaseData("DeletionDateTime", dateTime, false, "DeletedOn"); yield return new TestCaseData("AccountExpirationDate", dateTime, false, "AccountExpirationOn"); yield return new TestCaseData("RecordedAt", InputPrimitiveType.String, false, "RecordedAt"); yield return new TestCaseData("Date", InputPrimitiveType.PlainDate, false, "On"); yield return new TestCaseData("SnapshotTimestamp", dateTime.WithNullable(true), false, "SnapshotOn"); + yield return new TestCaseData("StatusTimestamp", dateTime.WithNullable(true), false, "StatusTimestamp"); + yield return new TestCaseData("LastSyncTimestamp", dateTime, false, "LastSyncOn"); + yield return new TestCaseData("TotalTime", dateTime, false, "TotalTime"); + yield return new TestCaseData("TopicTimestamp", dateTime.WithNullable(true), false, "TopicTimestamp"); yield return new TestCaseData("FromTime", dateTime, false, "FromTime"); yield return new TestCaseData("ToDate", dateTime, false, "ToDate"); yield return new TestCaseData("RecoveryPointInTime", dateTime, false, "RecoveryPointInTime"); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs index 3cf3937eac2..d184095d067 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/CSharpNameExtensionsTests.cs @@ -54,6 +54,28 @@ private static IEnumerable DateTimeNameTestCases() yield return new TestCaseData("expiresAt", dateTime, "expiresOn"); yield return new TestCaseData("deletedTime", dateTime, "deletedOn"); yield return new TestCaseData("finishedTime", dateTime, "finishedOn"); + yield return new TestCaseData("CreationTime", dateTime, "CreatedOn"); + yield return new TestCaseData("creationTime", dateTime, "createdOn"); + yield return new TestCaseData("ExpirationDateTime", dateTime, "ExpireOn"); + yield return new TestCaseData("expirationDateTime", dateTime, "expireOn"); + yield return new TestCaseData("AccountExpirationDate", dateTime, "AccountExpirationOn"); + yield return new TestCaseData("accountExpirationDate", dateTime, "accountExpirationOn"); + yield return new TestCaseData("RecreationTime", dateTime, "RecreationOn"); + yield return new TestCaseData("recreationTime", dateTime, "recreationOn"); + yield return new TestCaseData("TotalTime", dateTime, "TotalTime"); + yield return new TestCaseData("totalTime", dateTime, "totalTime"); + yield return new TestCaseData("TopicTimestamp", dateTime, "TopicTimestamp"); + yield return new TestCaseData("topicTimestamp", dateTime, "topicTimestamp"); + yield return new TestCaseData("TokenExpirationDate", dateTime, "TokenExpirationDate"); + yield return new TestCaseData("tokenExpirationDate", dateTime, "tokenExpirationDate"); + yield return new TestCaseData("FromageTime", dateTime, "FromageTime"); + yield return new TestCaseData("fromageTime", dateTime, "fromageTime"); + yield return new TestCaseData("StatusTimestamp", dateTime, "StatusTimestamp"); + yield return new TestCaseData("statusTimestamp", dateTime, "statusTimestamp"); + yield return new TestCaseData("StatusTimeStamp", dateTime, "StatusTimeStamp"); + yield return new TestCaseData("statusTimeStamp", dateTime, "statusTimeStamp"); + yield return new TestCaseData("LastSyncTimestamp", dateTime, "LastSyncOn"); + yield return new TestCaseData("lastSyncTimestamp", dateTime, "lastSyncOn"); yield return new TestCaseData("stateTransitionTime", dateTime, "stateTransitionOn"); yield return new TestCaseData("notBefore", dateTime, "notBefore"); }