Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SystemObjectProvider and type replacement #5487

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Generator.CSharp
{
internal static class StringExtensions
{
private static bool IsWordSeparator(char c) => !SyntaxFacts.IsIdentifierPartCharacter(c) || c == '_';
private static readonly Regex HumanizedCamelCaseRegex = new Regex(@"([A-Z])", RegexOptions.Compiled);

[return: NotNullIfNotNull("name")]
public static string ToCleanName(this string name, bool isCamelCase = true)
{
if (string.IsNullOrEmpty(name))
{
return name;
}
StringBuilder nameBuilder = new StringBuilder();

int i = 0;

if (char.IsDigit(name[0]))
{
nameBuilder.Append("_");
}
else
{
while (!SyntaxFacts.IsIdentifierStartCharacter(name[i]))
{
i++;
}
}

bool upperCase = false;
int firstWordLength = 1;
for (; i < name.Length; i++)
{
var c = name[i];
if (IsWordSeparator(c))
{
upperCase = true;
continue;
}

if (nameBuilder.Length == 0 && isCamelCase)
{
c = char.ToUpper(c);
upperCase = false;
}
else if (nameBuilder.Length < firstWordLength && !isCamelCase)
{
c = char.ToLower(c);
upperCase = false;
// grow the first word length when this letter follows by two other upper case letters
// this happens in OSProfile, where OS is the first word
if (i + 2 < name.Length && char.IsUpper(name[i + 1]) && (char.IsUpper(name[i + 2]) || IsWordSeparator(name[i + 2])))
firstWordLength++;
// grow the first word length when this letter follows by another upper case letter and an end of the string
// this happens when the string only has one word, like OS, DNS
if (i + 2 == name.Length && char.IsUpper(name[i + 1]))
firstWordLength++;
}

if (upperCase)
{
c = char.ToUpper(c);
upperCase = false;
}

nameBuilder.Append(c);
}

return nameBuilder.ToString();
}

[return: NotNullIfNotNull(nameof(name))]
public static string ToVariableName(this string name) => ToCleanName(name, isCamelCase: false);

public static GetPathPartsEnumerator GetFormattableStringFormatParts(string? format) => new GetPathPartsEnumerator(format);

public static GetPathPartsEnumerator GetFormattableStringFormatParts(ReadOnlySpan<char> format) => new GetPathPartsEnumerator(format);

public ref struct GetPathPartsEnumerator
{
private ReadOnlySpan<char> _path;
public Part Current { get; private set; }

public GetPathPartsEnumerator(ReadOnlySpan<char> format)
{
_path = format;
Current = default;
}

public readonly GetPathPartsEnumerator GetEnumerator() => this;

public bool MoveNext()
{
var span = _path;
if (span.Length == 0)
{
return false;
}

var separatorIndex = span.IndexOfAny('{', '}');

if (separatorIndex == -1)
{
Current = new Part(span, true);
_path = ReadOnlySpan<char>.Empty;
return true;
}

var separator = span[separatorIndex];
// Handle {{ and }} escape sequences
if (separatorIndex + 1 < span.Length && span[separatorIndex + 1] == separator)
{
Current = new Part(span.Slice(0, separatorIndex + 1), true);
_path = span.Slice(separatorIndex + 2);
return true;
}

var isLiteral = separator == '{';

// Skip empty literals
if (isLiteral && separatorIndex == 0 && span.Length > 1)
{
separatorIndex = span.IndexOf('}');
if (separatorIndex == -1)
{
Current = new Part(span.Slice(1), true);
_path = ReadOnlySpan<char>.Empty;
return true;
}

Current = new Part(span.Slice(1, separatorIndex - 1), false);
}
else
{
Current = new Part(span.Slice(0, separatorIndex), isLiteral);
}

_path = span.Slice(separatorIndex + 1);
return true;
}

public readonly ref struct Part
{
public Part(ReadOnlySpan<char> span, bool isLiteral)
{
Span = span;
IsLiteral = isLiteral;
}

public ReadOnlySpan<char> Span { get; }
public bool IsLiteral { get; }

public void Deconstruct(out ReadOnlySpan<char> span, out bool isLiteral)
{
span = Span;
isLiteral = IsLiteral;
}

public void Deconstruct(out ReadOnlySpan<char> span, out bool isLiteral, out int argumentIndex)
{
span = Span;
isLiteral = IsLiteral;

if (IsLiteral)
{
argumentIndex = -1;
}
else
{
var formatSeparatorIndex = span.IndexOf(':');
var indexSpan = formatSeparatorIndex == -1 ? span : span.Slice(0, formatSeparatorIndex);
argumentIndex = int.Parse(indexSpan);
}
}
}
}

/// <summary>
/// Determines if the given name is a C# keyword.
/// </summary>
/// <param name="name">The string name of the keyword.</param>
/// <returns><c>true</c> if the string is a csharp keyword.</returns>
public static bool IsCSharpKeyword(string? name)
{
if (name == null)
{
return false;
}

SyntaxKind kind = SyntaxFacts.GetKeywordKind(name);
if (kind == SyntaxKind.None)
{
kind = SyntaxFacts.GetContextualKeywordKind(name);
}

return SyntaxFacts.IsKeywordKind(kind);
}

public static string ToApiVersionMemberName(this string version)
{
var sb = new StringBuilder("V");
int startIndex = version.StartsWith("v", StringComparison.InvariantCultureIgnoreCase) ? 1 : 0;

for (int i = startIndex; i < version.Length; i++)
{
char c = version[i];
if (c == '-' || c == '.')
{
sb.Append('_');
}
else
{
sb.Append(c);
}
}

return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(sb.ToString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using Microsoft.Generator.CSharp.Input;

namespace Microsoft.TypeSpec.Generator.Expressions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private static TypeProvider[] BuildModels()
foreach (var inputModel in input.Models)
{
var outputModel = CodeModelPlugin.Instance.TypeFactory.CreateModel(inputModel);
if (outputModel != null)
if (outputModel != null && outputModel is not SystemObjectProvider)
{
models.Add(outputModel);
var unknownVariant = inputModel.DiscriminatedSubtypes.Values.FirstOrDefault(m => m.IsUnknownDiscriminatorModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private IReadOnlyList<ModelProvider> BuildDerivedModels()
// add discriminated subtypes
foreach (var subtype in _inputModel.DiscriminatedSubtypes)
{
var model = CodeModelPlugin.Instance.TypeFactory.CreateModel(subtype.Value);
var model = CodeModelPlugin.Instance.TypeFactory.CreateModel(subtype.Value) as ModelProvider;
if (model != null)
{
derivedModels.Add(model);
Expand All @@ -67,7 +67,7 @@ private IReadOnlyList<ModelProvider> BuildDerivedModels()
// add derived models
foreach (var derivedModel in _inputModel.DerivedModels)
{
var model = CodeModelPlugin.Instance.TypeFactory.CreateModel(derivedModel);
var model = CodeModelPlugin.Instance.TypeFactory.CreateModel(derivedModel) as ModelProvider;
if (model != null)
{
derivedModels.Add(model);
Expand All @@ -76,7 +76,23 @@ private IReadOnlyList<ModelProvider> BuildDerivedModels()

return [.. derivedModels];
}
internal override TypeProvider? BaseTypeProvider => BaseModelProvider;

internal override TypeProvider? BaseTypeProvider
{
get
{
if (_baseTypeProvider?.Value is ModelProvider modelProvider)
{
return modelProvider;
}
else if (_baseTypeProvider?.Value is SystemObjectProvider systemObjectProvider)
{
return systemObjectProvider;
}

return null;
}
}

public ModelProvider? BaseModelProvider
=> _baseModelProvider ??= (_baseTypeProvider?.Value is ModelProvider baseModelProvider ? baseModelProvider : null);
Expand All @@ -93,7 +109,7 @@ protected override string BuildNamespace() => string.IsNullOrEmpty(_inputModel.N

protected override CSharpType? GetBaseType()
{
return BaseModelProvider?.Type;
return BaseTypeProvider?.Type;
}

protected override TypeProvider[] BuildSerializationProviders()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ protected PropertyProvider()

internal static bool TryCreate(InputModelProperty inputProperty, TypeProvider enclosingType, [NotNullWhen(true)] out PropertyProvider? property)
{
var inputPropertyType = inputProperty.Type as InputModelType;
if (inputPropertyType != null && CodeModelPlugin.Instance.TypeFactory.TryGetPropertyTypeReplacement(inputPropertyType, out var replacement))
{
property = new PropertyProvider(inputProperty, replacement.Type, enclosingType);
return true;
}

var type = CodeModelPlugin.Instance.TypeFactory.CreateCSharpType(inputProperty.Type);
if (type == null)
{
Expand Down
Loading
Loading