Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1316,11 +1316,11 @@ internal static List<ParameterProvider> GetMethodParameters(
{
if (methodType == ScmMethodKind.CreateRequest)
{
parameter = ScmKnownParameters.CreateRequestContent(inputParam);
parameter = ScmKnownParameters.CreateRequestContent();
}
else
{
parameter = ScmKnownParameters.CreateRequestContent(inputParam,
parameter = ScmKnownParameters.CreateRequestContent(
optional: parameter.DefaultValue != null);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ public void XmlDeserializationHandlesDateTimeOffsetProperty()
Assert.IsNotNull(xmlDeserializationMethod);
var methodBody = xmlDeserializationMethod!.BodyStatements!.ToDisplayString();

Assert.IsTrue(methodBody.Contains("timestamp = 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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Comment thread
jorgerangel-msft marked this conversation as resolved.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public sealed class ParameterProvider : IEquatable<ParameterProvider>
public ParameterProvider(InputParameter inputParameter)
{
InputParameter = inputParameter;
Name = inputParameter.Name;
Name = inputParameter is InputMethodParameter && !inputParameter.IsExactName
&& inputParameter.Type.IsDateTimeInputType()
? inputParameter.Name.NormalizeDateTimeSuffix()
: inputParameter.Name;
Comment thread
jorgerangel-msft marked this conversation as resolved.
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T
(lastContractProperties is null ||
!lastContractProperties.Any(p => p.Name == legacyName)))
{
identifierName = identifierName.NormalizeCSharpAcronyms();
identifierName = identifierName
.NormalizeCSharpAcronyms(inputProperty.Type.IsDateTimeInputType());
}
Name = identifierName == enclosingType.Name
? $"{identifierName}Property"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.TypeSpec.Generator.Input;

namespace Microsoft.TypeSpec.Generator.Utilities
{
Expand All @@ -20,8 +21,9 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu
("Os", "OS")
Comment thread
jorgerangel-msft marked this conversation as resolved.
];

public static string NormalizeCSharpAcronyms(this string name)
public static string NormalizeCSharpAcronyms(this string name, bool normalizeDateTimeSuffix = false)
Comment thread
jorgerangel-msft marked this conversation as resolved.
{
name = normalizeDateTimeSuffix ? name.NormalizeDateTimeSuffix() : name;
StringBuilder? normalizedName = null;
int segmentStart = 0;
for (int index = 0; index < name.Length - 1; index++)
Expand Down Expand Up @@ -57,6 +59,85 @@ public static string NormalizeCSharpAcronyms(this string name)
return normalizedName.ToString();
}

public static string NormalizeDateTimeSuffix(this string name)
{
if (DateTimeNameRules.HasExcludedComponent(name))
{
return name;
}

var suffixLength = DateTimeNameRules.GetSuffixLength(name);
if (suffixLength == 0)
{
return name;
}

var prefix = name[..^suffixLength];
var onSuffix = prefix.Length == 0 && char.IsLower(name[0])
? DateTimeNameRules.LowercaseOnSuffix
: DateTimeNameRules.OnSuffix;
return prefix + onSuffix;
}

private static class DateTimeNameRules
{
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 name.StartsWith(FromName, StringComparison.OrdinalIgnoreCase) ||
name.StartsWith(ToName, StringComparison.OrdinalIgnoreCase) ||
name.EndsWith(PointInTimeName, StringComparison.OrdinalIgnoreCase);
}

internal static int GetSuffixLength(string name)
{
if (name.EndsWith(TimestampSuffix, StringComparison.Ordinal) ||
name.EndsWith(TimeStampSuffix, StringComparison.Ordinal) ||
name.Equals(TimestampSuffix, StringComparison.OrdinalIgnoreCase))
{
return TimestampSuffix.Length;
}

if (name.Length > DateTimeSuffix.Length && name.EndsWith(DateTimeSuffix, StringComparison.Ordinal))
{
return DateTimeSuffix.Length;
}

if (name.Length > TimeSuffix.Length && name.EndsWith(TimeSuffix, StringComparison.Ordinal))
{
return TimeSuffix.Length;
}

if (name.Equals(DateSuffix, StringComparison.OrdinalIgnoreCase) ||
name.EndsWith(DateSuffix, StringComparison.Ordinal))
{
return DateSuffix.Length;
}

return name.Length > AtSuffix.Length && name.EndsWith(AtSuffix, StringComparison.Ordinal)
? AtSuffix.Length
: 0;
}
}

public static bool IsDateTimeInputType(this InputType inputType) => inputType switch
{
InputDateTimeType => true,
InputPrimitiveType { Kind: InputPrimitiveTypeKind.PlainDate } => true,
InputNullableType nullableType => IsDateTimeInputType(nullableType.Type),
_ => false
};
[return: NotNullIfNotNull(nameof(name))]
public static string? NormalizeCSharpUrlSuffix(this string? name)
=> !string.IsNullOrEmpty(name) && name.EndsWith("Url", StringComparison.Ordinal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -189,13 +190,9 @@ 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)
{
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,64 @@ public void ValidateArrayHandling()
Assert.IsTrue(parameter.ToPublicInputParameter().Type.Equals(typeof(IEnumerable<string>)));
}

[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<InputType> ValueInputTypes()
{
yield return InputPrimitiveType.Int32;
yield return InputPrimitiveType.Float32;
yield return InputFactory.Int32Enum("inputEnum", [("foo", 1)], isExtensible: true);
}

private static IEnumerable<TestCaseData> DateTimeParameterNameTestCases()
{
var dateTime = new InputDateTimeType(
DateTimeKnownEncoding.Rfc3339,
"utcDateTime",
"TypeSpec.utcDateTime",
InputPrimitiveType.String);

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<TestCaseData> NotEqualsTestCases()
{
yield return new TestCaseData(
Expand Down
Loading
Loading