protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); @namespace.Content = "AspNetCore"; var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; if (string.IsNullOrEmpty(filePath)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. var checksum = BytesToString(codeDocument.Source.GetChecksum()); @class.ClassName = $"AspNetCore_{checksum}"; } else { @class.ClassName = CSharpIdentifier.GetClassNameFromPath(filePath); } @class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>"; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); method.MethodName = "ExecuteAsync"; method.Modifiers.Clear(); method.Modifiers.Add("public"); method.Modifiers.Add("async"); method.Modifiers.Add("override"); method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; }
private void CreateTypeInferenceMethod(DocumentIntermediateNode documentNode, ComponentIntermediateNode node) { var @namespace = documentNode.FindPrimaryNamespace().Content; @namespace = string.IsNullOrEmpty(@namespace) ? "__Blazor" : "__Blazor." + @namespace; @namespace += "." + documentNode.FindPrimaryClass().ClassName; var typeInferenceNode = new ComponentTypeInferenceMethodIntermediateNode() { Component = node, // Method name is generated and guaranteed not to collide, since it's unique for each // component call site. MethodName = $"Create{CSharpIdentifier.SanitizeIdentifier(node.TagName)}_{_id++}", FullTypeName = @namespace + ".TypeInference", }; node.TypeInferenceNode = typeInferenceNode; // Now we need to insert the type inference node into the tree. var namespaceNode = documentNode.Children .OfType <NamespaceDeclarationIntermediateNode>() .Where(n => n.Annotations.Contains(new KeyValuePair <object, object>(ComponentMetadata.Component.GenericTypedKey, bool.TrueString))) .FirstOrDefault(); if (namespaceNode == null) { namespaceNode = new NamespaceDeclarationIntermediateNode() { Annotations = { { ComponentMetadata.Component.GenericTypedKey, bool.TrueString }, }, Content = @namespace, }; documentNode.Children.Add(namespaceNode); } var classNode = namespaceNode.Children .OfType <ClassDeclarationIntermediateNode>() .Where(n => n.ClassName == "TypeInference") .FirstOrDefault(); if (classNode == null) { classNode = new ClassDeclarationIntermediateNode() { ClassName = "TypeInference", Modifiers = { "internal", "static", }, }; namespaceNode.Children.Add(classNode); } classNode.Children.Add(typeInferenceNode); }
public string GenerateEnumDefinition(ApiEnum apiEnum) { var indent = new Indent(); var builder = new StringBuilder(); var typeNameForEnum = apiEnum.ToCSharpClassName(); if (apiEnum.Deprecation != null) { builder.AppendLine(apiEnum.Deprecation.ToCSharpDeprecation()); } builder.AppendLine($"{indent}[JsonConverter(typeof(EnumStringConverter))]"); builder.AppendLine($"{indent}public enum {typeNameForEnum}"); builder.AppendLine($"{indent}{{"); indent.Increment(); foreach (var value in apiEnum.Values) { var identifierForValue = CSharpIdentifier.ForClassOrNamespace(value); builder.AppendLine($"{indent}[EnumMember(Value = \"{value}\")]"); builder.AppendLine($"{indent}{identifierForValue},"); builder.AppendLine($"{indent}"); } indent.Decrement(); builder.AppendLine($"{indent}}}"); return(builder.ToString()); }
[InlineData("\u2062", "_\u2062")] // UnicodeCategory.Format (invisible times) public void SanitizeIdentifier_AddsUnderscore_WhenItShould(string input, string expectdOutput) { // Arrange and Act var output = CSharpIdentifier.SanitizeIdentifier(input); // Assert Assert.Equal(expectdOutput, output); }
[InlineData("\uFF1C", "_")] // UnicodeCategory.MathSymbol (fullwidth less-than sign) public void SanitizeIdentifier_DoesNotAddUnderscore_WhenInvalidCharacter(string input, string expectdOutput) { // Arrange and Act var output = CSharpIdentifier.SanitizeIdentifier(input); // Assert Assert.Equal(expectdOutput, output); }
[InlineData("aa\uFF1C\uFF1C\uFF1Cbb", "aa___bb")] // UnicodeCategory.MathSymbol (fullwidth less-than sign) public void SanitizeIdentifier_ReplacesInvalidCharacters_WhenNotFirst(string input, string expectdOutput) { // Arrange and Act var output = CSharpIdentifier.SanitizeIdentifier(input); // Assert Assert.Equal(expectdOutput, output); }
[InlineData('\uFF1C')] // UnicodeCategory.MathSymbol (fullwidth less-than sign) public void IsIdentifierPart_ReturnsFalse_WhenItShould(char character) { // Arrange and Act var result = CSharpIdentifier.IsIdentifierPart(character); // Assert Assert.False(result); }
public static string ToCSharpFactoryMethodName(this ApiDto subject, ApiDto parent) { var classNameForParent = CSharpIdentifier.ForClassOrNamespace(parent.Name); var classNameForSubject = CSharpIdentifier.ForClassOrNamespace(subject.Name); var factoryMethodName = CSharpIdentifier.ForClassOrNamespace( classNameForSubject.Replace(classNameForParent, string.Empty)); return(factoryMethodName); }
public void TypeForArrayOfUri() { _namingStrategy .TypeFor(new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Type = "string", Format = "uri" } }) .Should().BeEquivalentTo(CSharpIdentifier.ListOf(CSharpIdentifier.Uri)); }
public static string?ToCSharpRequestBodyClassName(this ApiEndpoint subject, string endpointPath) { if (subject.RequestBody == null || subject.RequestBody.Kind != ApiFieldType.Object.ObjectKind.REQUEST_BODY) { return(null); } return(CSharpIdentifier.ForClassOrNamespace(endpointPath) + subject.Method.ToHttpMethod().ToLowerInvariant().ToUppercaseFirst() + "Request"); }
protected override CSharpIdentifier GetImplementation(TEndpoint endpoint, ITypeList typeList) { var identifier = new CSharpIdentifier(TypeNamespace, TypeName); identifier.TypeArguments.Add(typeList.For(endpoint.Schema)); if (endpoint.Element != null) { identifier.TypeArguments.Add(typeList.ImplementationFor(endpoint.Element)); } return(identifier); }
public override CSharpIdentifier GetInterface(TEndpoint endpoint, ITypeList typeList) { var identifier = new CSharpIdentifier(TypeNamespace, TypeName).ToInterface(); identifier.TypeArguments.Add(typeList.For(endpoint.Schema)); if (endpoint.Element != null) { identifier.TypeArguments.Add(typeList.InterfaceFor(endpoint.Element)); } return(identifier); }
private bool TryComputeClassName(RazorCodeDocument codeDocument, out string className) { className = null; if (codeDocument.Source.FilePath == null || codeDocument.Source.RelativePath == null) { return(false); } var relativePath = NormalizePath(codeDocument.Source.RelativePath); className = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath)); return(true); }
private static string GetFeaturePopulatorClassName(Compilation compilation) { // note assemblyname can be null if not specified // if it is, we fall back to previous behavior, using a random part generated by a guid if (string.IsNullOrEmpty(compilation.AssemblyName)) { return(CodeGenerator.ToolName + Guid.NewGuid().ToString("N").Substring(0, 10) + ClassSuffix); } // generate classname based on assemblyname var partialClassName = CSharpIdentifier.SanitizeClassName(compilation.AssemblyName); return(CodeGenerator.ToolName + partialClassName + ClassSuffix); }
private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class) { if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) { @namespace = null; @class = null; return(false); } // Try and infer a namespace from the project directory. We don't yet have the ability to pass // the namespace through from the project. var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1); var baseDirectory = filePath.Substring(0, filePath.Length - trimLength); var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators); var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1); if (string.IsNullOrEmpty(baseNamespace)) { @namespace = null; @class = null; return(false); } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0])); for (var i = 1; i < segments.Length; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); } segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries); // Skip the last segment because it's the FileName. for (var i = 0; i < segments.Length - 1; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); } @namespace = builder.ToString(); @class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath)); return(true); }
// In general documents will have a relative path (relative to the project root). // We can only really compute a nice class/namespace when we know a relative path. // // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't // set up correctly. private bool TryComputeNamespaceAndClass( RazorCodeGenerationOptions options, string filePath, string relativePath, out string @namespace, out string @class) { if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) { @namespace = null; @class = null; return(false); } var rootNamespace = options.RootNamespace; if (string.IsNullOrEmpty(rootNamespace)) { @namespace = null; @class = null; return(false); } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. var segments = rootNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0])); for (var i = 1; i < segments.Length; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); } segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries); // Skip the last segment because it's the FileName. for (var i = 0; i < segments.Length - 1; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); } @namespace = builder.ToString(); @class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath)); return(true); }
private static CSharpProperty Property(string name, string relativeUri, CSharpIdentifier implementationType, CSharpIdentifier interfaceType = null, string description = null) => new CSharpProperty(interfaceType ?? implementationType.ToInterface(), name) { GetterExpression = new CSharpClassConstruction(implementationType) { Parameters = { Referrer, new CSharpParameter(CSharpIdentifier.String, "relativeUri") { Value = relativeUri } } }, Description = description };
public static string ToCSharpPropertyName(this ApiField subject, string?containingType) { var propertyName = CSharpIdentifier.ForClassOrNamespace(subject.Name); return(subject.Type switch { ApiFieldType.Primitive primitive when primitive.ToCSharpPrimitiveType() == CSharpType.Bool && !propertyName.StartsWith("Is") && !propertyName.StartsWith("Can") => $"Is{propertyName}", // Resolve CS0542 - Member names cannot be the same as their enclosing type by adding prefix/suffix ApiFieldType.Array _ when string.Equals(propertyName, containingType, StringComparison.OrdinalIgnoreCase) => $"{propertyName}Items", _ => propertyName });
// internal for testing. // // This code does a best-effort attempt to compute a namespace 'suffix' - the path difference between // where the @namespace directive appears and where the current document is on disk. // // In the event that these two source either don't have FileNames set or don't follow a coherent hierarchy, // we will just use the namespace verbatim. internal static string GetNamespace(string source, DirectiveIntermediateNode directive) { var directiveSource = NormalizeDirectory(directive.Source?.FilePath); var baseNamespace = directive.Tokens.FirstOrDefault()?.Content; if (string.IsNullOrEmpty(baseNamespace)) { // The namespace directive was incomplete. return(string.Empty); } if (string.IsNullOrEmpty(source) || directiveSource == null) { // No sources, can't compute a suffix. return(baseNamespace); } // We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive. if (!source.StartsWith(directiveSource, StringComparison.OrdinalIgnoreCase) || source.Length <= directiveSource.Length) { // The imports are not from the directory hierarchy, can't compute a suffix. return(baseNamespace); } // OK so that this point we know that the 'imports' file containing this directive is in the directory // hierarchy of this soure file. This is the case where we can append a suffix to the baseNamespace. // // Everything so far has just been defensiveness on our part. var builder = new StringBuilder(baseNamespace); var segments = source.Substring(directiveSource.Length).Split(Separators); // Skip the last segment because it's the FileName. for (var i = 0; i < segments.Length - 1; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeClassName(segments[i])); } return(builder.ToString()); }
public override IClassNameConvertible Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { return(null); } var readerAtStart = reader; using var jsonDocument = JsonDocument.ParseValue(ref reader); var jsonObject = jsonDocument.RootElement; var className = jsonObject.GetStringValue("className"); if (!string.IsNullOrEmpty(className)) { if (!TypeMap.TryGetValue(className, out var targetType)) { targetType = Type.GetType(SpaceDotNetClientNamespace + "." + CSharpIdentifier.ForClassOrNamespace(className) + ", " + SpaceDotNetClientAssemblyName); if (targetType != null) { TypeMap[className] = targetType; } } if (targetType != null && typeof(IClassNameConvertible).IsAssignableFrom(targetType)) { var value = JsonSerializer.Deserialize(ref readerAtStart, targetType, options) as IClassNameConvertible; PropagatePropertyAccessPathHelper.SetAccessPathForValue(targetType.Name, true, value); return(value); } } return(null); }
private static CSharpClass GenerateSchema(CSharpIdentifier identifier, TypeList typeList) { // TODO: Proper implementation return(new CSharpClass(identifier)); }
public void SanitizeClassNameTest(string inputName, string expected) { var sanitized = CSharpIdentifier.SanitizeClassName(inputName); Assert.Equal(expected, sanitized); }
public static string ToCSharpClassName(this ApiUrlParameterOption subject) => CSharpIdentifier.ForClassOrNamespace(subject.OptionName);
private static CSharpIdentifier CollectionEndpoint(CSharpClass dto, CSharpIdentifier elementEndpoint) => new CSharpIdentifier("TypedRest.Endpoints.Generic", "CollectionEndpoint") { TypeArguments = { dto.Identifier, elementEndpoint } };
public static string ToCSharpClassNameShort(this ApiUrlParameterOption subject) => CSharpIdentifier.ForClassOrNamespace(subject.OptionName.Split('.', StringSplitOptions.RemoveEmptyEntries).Last());
protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); if (!TryComputeNamespaceAndClass( codeDocument.Source.FilePath, codeDocument.Source.RelativePath, out var computedNamespace, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. computedNamespace = "AspNetCore"; var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); computedClass = $"AspNetCore_{checksum}"; } @namespace.Content = computedNamespace; @class.ClassName = computedClass; @class.BaseType = $"global::{CodeGenerationConstants.RazorComponent.FullTypeName}"; var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; if (string.IsNullOrEmpty(filePath)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); @class.ClassName = $"AspNetCore_{checksum}"; } else { @class.ClassName = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath)); } @class.Modifiers.Clear(); @class.Modifiers.Add("public"); @class.Modifiers.Add("sealed"); method.MethodName = CodeGenerationConstants.RazorComponent.BuildRenderTree; method.ReturnType = "void"; method.Modifiers.Clear(); method.Modifiers.Add("public"); method.Modifiers.Add("override"); method.Parameters.Clear(); method.Parameters.Add(new MethodParameter() { TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName, ParameterName = CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter, }); // We need to call the 'base' method as the first statement. var callBase = new CSharpCodeIntermediateNode(); callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); callBase.Children.Add(new IntermediateToken { Kind = TokenKind.CSharp, Content = $"base.{CodeGenerationConstants.RazorComponent.BuildRenderTree}({CodeGenerationConstants.RazorComponent.BuildRenderTreeParameter});" }); method.Children.Insert(0, callBase); }
public static string ToCSharpVariableName(this ApiField subject) => CSharpIdentifier.ForVariable(subject.Name);
public static string ToCSharpClassName(this ApiDto subject) => CSharpIdentifier.ForClassOrNamespace(subject.Name);
public static string ToCSharpBackingFieldName(this ApiField subject) => CSharpIdentifier.ForBackingField(subject.Name);
private string GenerateMenuIds(Node node) { var indent = new Indent(); var builder = new StringBuilder(); var lastDotIndexInPrefix = node.Prefix.LastIndexOf(".", StringComparison.Ordinal); var commonPrefix = lastDotIndexInPrefix >= 0 ? CSharpIdentifier.ForClassOrNamespace(node.Prefix.Substring(lastDotIndexInPrefix + 1)) : CSharpIdentifier.ForClassOrNamespace(node.Prefix); if (node.Children.Count <= 1) { var prefix = node.Children.Count > 0 ? commonPrefix : "Root"; var expectedPayload = string.Empty; if (node.Context != null && _context.TryGetDto(node.Context.Id, out var expectedPayloadDto) && expectedPayloadDto != null) { expectedPayload = expectedPayloadDto.ToCSharpClassName(); } builder.AppendLine($"{indent}/// <summary>"); builder.AppendLine($"{indent}/// Represents the \"{node.Prefix}\" menu."); if (!string.IsNullOrEmpty(expectedPayload) && expectedPayload != CSharpType.Object.Value) { builder.AppendLine($"{indent}///"); builder.AppendLine($"{indent}/// Expected webhook payload: <see cref=\"{expectedPayload}\"/>."); } builder.AppendLine($"{indent}/// </summary>"); builder.AppendLine($"{indent}public static readonly string {prefix} = \"{node.Prefix}\";"); builder.AppendLine($"{indent}"); } else { if (!string.IsNullOrEmpty(node.Prefix)) { builder.AppendLine($"{indent}public static class {commonPrefix}"); builder.AppendLine($"{indent}{{"); indent.Increment(); } foreach (var nodeChild in node.Children) { builder.Append( indent.Wrap( GenerateMenuIds(nodeChild))); } if (!string.IsNullOrEmpty(node.Prefix)) { indent.Decrement(); builder.AppendLine($"{indent}}}"); builder.AppendLine($"{indent}"); } } return(builder.ToString()); }