Beispiel #1
0
    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());
        }
Beispiel #4
0
    [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);
    }
Beispiel #5
0
    [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);
    }
Beispiel #6
0
    [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);
    }
Beispiel #7
0
    [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);
    }
Beispiel #8
0
        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);
        }
Beispiel #14
0
        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
            });
Beispiel #19
0
    // 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);
        }
Beispiel #21
0
 private static CSharpClass GenerateSchema(CSharpIdentifier identifier, TypeList typeList)
 {
     // TODO: Proper implementation
     return(new CSharpClass(identifier));
 }
Beispiel #22
0
        public void SanitizeClassNameTest(string inputName, string expected)
        {
            var sanitized = CSharpIdentifier.SanitizeClassName(inputName);

            Assert.Equal(expected, sanitized);
        }
Beispiel #23
0
 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 }
 };
Beispiel #25
0
 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);
Beispiel #28
0
 public static string ToCSharpClassName(this ApiDto subject)
 => CSharpIdentifier.ForClassOrNamespace(subject.Name);
 public static string ToCSharpBackingFieldName(this ApiField subject)
 => CSharpIdentifier.ForBackingField(subject.Name);
Beispiel #30
0
        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());
        }