private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
        {
            var typeName     = type.ToDisplayString(FullNameTypeDisplayFormat);
            var assemblyName = type.ContainingAssembly.Identity.Name;

            var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Component.TagHelperKind, typeName, assemblyName);

            builder.SetTypeName(typeName);

            // This opts out this 'component' tag helper for any processing that's specific to the default
            // Razor ITagHelper runtime.
            builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Component.RuntimeName;

            if (type.IsGenericType)
            {
                builder.Metadata[BlazorMetadata.Component.GenericTypedKey] = bool.TrueString;

                for (var i = 0; i < type.TypeArguments.Length; i++)
                {
                    var typeParameter = type.TypeArguments[i] as ITypeParameterSymbol;
                    if (typeParameter != null)
                    {
                        CreateTypeParameterProperty(builder, typeParameter);
                    }
                }
            }

            var xml = type.GetDocumentationCommentXml();

            if (!string.IsNullOrEmpty(xml))
            {
                builder.Documentation = xml;
            }

            // Components have very simple matching rules. The type name (short) matches the tag name.
            builder.TagMatchingRule(r => r.TagName = type.Name);

            foreach (var property in GetProperties(symbols, type))
            {
                if (property.kind == PropertyKind.Ignored)
                {
                    continue;
                }

                CreateProperty(builder, property.property, property.kind);
            }

            if (builder.BoundAttributes.Any(a => a.IsParameterizedChildContentProperty()) &&
                !builder.BoundAttributes.Any(a => string.Equals(a.Name, BlazorMetadata.ChildContent.ParameterAttributeName, StringComparison.OrdinalIgnoreCase)))
            {
                // If we have any parameterized child content parameters, synthesize a 'Context' parameter to be
                // able to set the variable name (for all child content). If the developer defined a 'Context' parameter
                // already, then theirs wins.
                CreateContextParameter(builder, childContentName: null);
            }

            var descriptor = builder.Build();

            return(descriptor);
        }
        private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
        {
            var typeName     = type.ToDisplayString(FullNameTypeDisplayFormat);
            var assemblyName = type.ContainingAssembly.Identity.Name;

            var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Component.TagHelperKind, typeName, assemblyName);

            builder.SetTypeName(typeName);

            // This opts out this 'component' tag helper for any processing that's specific to the default
            // Razor ITagHelper runtime.
            builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Component.RuntimeName;

            if (type.IsGenericType)
            {
                builder.Metadata[BlazorMetadata.Component.GenericTypedKey] = bool.TrueString;

                for (var i = 0; i < type.TypeArguments.Length; i++)
                {
                    var typeParameter = type.TypeArguments[i] as ITypeParameterSymbol;
                    if (typeParameter != null)
                    {
                        CreateTypeParameterProperty(builder, typeParameter);
                    }
                }
            }

            var xml = type.GetDocumentationCommentXml();

            if (!string.IsNullOrEmpty(xml))
            {
                builder.Documentation = xml;
            }

            // Components have very simple matching rules. The type name (short) matches the tag name.
            builder.TagMatchingRule(r => r.TagName = type.Name);

            foreach (var property in GetProperties(symbols, type))
            {
                if (property.kind == PropertyKind.Ignored)
                {
                    continue;
                }

                CreateProperty(builder, property.property, property.kind);
            }

            var descriptor = builder.Build();

            return(descriptor);
        }
        public void Execute(TagHelperDescriptorProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var compilation = context.GetCompilation();

            if (compilation == null)
            {
                // No compilation, nothing to do.
                return;
            }

            // We need to see private members too
            compilation = WithMetadataImportOptionsAll(compilation);

            var symbols = BlazorSymbols.Create(compilation);

            var types   = new List <INamedTypeSymbol>();
            var visitor = new ComponentTypeVisitor(symbols, types);

            // Visit the primary output of this compilation, as well as all references.
            visitor.Visit(compilation.Assembly);
            foreach (var reference in compilation.References)
            {
                // We ignore .netmodules here - there really isn't a case where they are used by user code
                // even though the Roslyn APIs all support them.
                if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
                {
                    visitor.Visit(assembly);
                }
            }

            for (var i = 0; i < types.Count; i++)
            {
                var type       = types[i];
                var descriptor = CreateDescriptor(symbols, type);
                context.Results.Add(descriptor);

                foreach (var childContent in descriptor.GetChildContentProperties())
                {
                    // Synthesize a separate tag helper for each child content property that's declared.
                    context.Results.Add(CreateChildContentDescriptor(symbols, descriptor, childContent));
                }
            }
        }
        private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
        {
            var typeName     = component.GetTypeName() + "." + attribute.Name;
            var assemblyName = component.AssemblyName;

            var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.ChildContent.TagHelperKind, typeName, assemblyName);

            builder.SetTypeName(typeName);

            // This opts out this 'component' tag helper for any processing that's specific to the default
            // Razor ITagHelper runtime.
            builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.ChildContent.RuntimeName;

            // Opt out of processing as a component. We'll process this specially as part of the component's body.
            builder.Metadata[BlazorMetadata.SpecialKindKey] = BlazorMetadata.ChildContent.TagHelperKind;

            var xml = attribute.Documentation;

            if (!string.IsNullOrEmpty(xml))
            {
                builder.Documentation = xml;
            }

            // Child content matches the property name, but only as a direct child of the component.
            builder.TagMatchingRule(r =>
            {
                r.TagName   = attribute.Name;
                r.ParentTag = component.TagMatchingRules.First().TagName;
            });

            if (attribute.IsParameterizedChildContentProperty())
            {
                // For child content attributes with a parameter, synthesize an attribute that allows you to name
                // the parameter.
                builder.BindAttribute(b =>
                {
                    b.Name          = "Context";
                    b.TypeName      = typeof(string).FullName;
                    b.Documentation = string.Format(Resources.ChildContentParameterName_Documentation, attribute.Name);
                });
            }

            var descriptor = builder.Build();

            return(descriptor);
        }
            public static BlazorSymbols Create(Compilation compilation)
            {
                var symbols = new BlazorSymbols();

                symbols.BlazorComponent = compilation.GetTypeByMetadataName(BlazorApi.BlazorComponent.MetadataName);
                if (symbols.BlazorComponent == null)
                {
                    // No definition for BlazorComponent, nothing to do.
                    return(null);
                }

                symbols.IComponent = compilation.GetTypeByMetadataName(BlazorApi.IComponent.MetadataName);
                if (symbols.IComponent == null)
                {
                    // No definition for IComponent, nothing to do.
                    return(null);
                }

                symbols.ParameterAttribute = compilation.GetTypeByMetadataName(BlazorApi.ParameterAttribute.MetadataName);
                if (symbols.ParameterAttribute == null)
                {
                    // No definition for [Parameter], nothing to do.
                    return(null);
                }

                symbols.RenderFragment = compilation.GetTypeByMetadataName(BlazorApi.RenderFragment.MetadataName);
                if (symbols.RenderFragment == null)
                {
                    // No definition for RenderFragment, nothing to do.
                }

                symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(BlazorApi.RenderFragmentOfT.MetadataName);
                if (symbols.RenderFragmentOfT == null)
                {
                    // No definition for RenderFragment, nothing to do.
                }

                return(symbols);
            }
 public ComponentTypeVisitor(BlazorSymbols symbols, List <INamedTypeSymbol> results)
 {
     _symbols = symbols;
     _results = results;
 }
        // Does a walk up the inheritance chain to determine the set of parameters by using
        // a dictionary keyed on property name.
        //
        // We consider parameters to be defined by properties satisfying all of the following:
        // - are visible (not shadowed)
        // - have the [Parameter] attribute
        // - have a setter, even if private
        // - are not indexers
        private IEnumerable <(IPropertySymbol property, PropertyKind kind)> GetProperties(BlazorSymbols symbols, INamedTypeSymbol type)
        {
            var properties = new Dictionary <string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);

            do
            {
                if (type == symbols.BlazorComponent)
                {
                    // The BlazorComponent base class doesn't have any [Parameter].
                    // Bail out now to avoid walking through its many members, plus the members
                    // of the System.Object base class.
                    break;
                }

                var members = type.GetMembers();
                for (var i = 0; i < members.Length; i++)
                {
                    var property = members[i] as IPropertySymbol;
                    if (property == null)
                    {
                        // Not a property
                        continue;
                    }

                    if (properties.ContainsKey(property.Name))
                    {
                        // Not visible
                        continue;
                    }

                    var kind = PropertyKind.Default;
                    if (property.Parameters.Length != 0)
                    {
                        // Indexer
                        kind = PropertyKind.Ignored;
                    }

                    if (property.SetMethod == null)
                    {
                        // No setter
                        kind = PropertyKind.Ignored;
                    }

                    if (property.IsStatic)
                    {
                        kind = PropertyKind.Ignored;
                    }

                    if (!property.GetAttributes().Any(a => a.AttributeClass == symbols.ParameterAttribute))
                    {
                        // Does not have [Parameter]
                        kind = PropertyKind.Ignored;
                    }

                    if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Enum)
                    {
                        kind = PropertyKind.Enum;
                    }

                    if (kind == PropertyKind.Default && property.Type == symbols.RenderFragment)
                    {
                        kind = PropertyKind.ChildContent;
                    }

                    if (kind == PropertyKind.Default &&
                        property.Type is INamedTypeSymbol namedType &&
                        namedType.IsGenericType &&
                        namedType.ConstructedFrom == symbols.RenderFragmentOfT)
                    {
                        kind = PropertyKind.ChildContent;
                    }

                    if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate)
                    {
                        kind = PropertyKind.Delegate;
                    }

                    properties.Add(property.Name, (property, kind));
                }

                type = type.BaseType;
            }while (type != null);

            return(properties.Values);
        }
Example #8
0
        private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
        {
            var typeName     = type.ToDisplayString(FullNameTypeDisplayFormat);
            var assemblyName = type.ContainingAssembly.Identity.Name;

            var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Component.TagHelperKind, typeName, assemblyName);

            builder.SetTypeName(typeName);

            // This opts out this 'component' tag helper for any processing that's specific to the default
            // Razor ITagHelper runtime.
            builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Component.RuntimeName;

            var xml = type.GetDocumentationCommentXml();

            if (!string.IsNullOrEmpty(xml))
            {
                builder.Documentation = xml;
            }

            // Components have very simple matching rules. The type name (short) matches the tag name.
            builder.TagMatchingRule(r => r.TagName = type.Name);

            foreach (var property in GetProperties(symbols, type))
            {
                if (property.kind == PropertyKind.Ignored)
                {
                    continue;
                }

                builder.BindAttribute(pb =>
                {
                    pb.Name     = property.property.Name;
                    pb.TypeName = property.property.Type.ToDisplayString(FullNameTypeDisplayFormat);
                    pb.SetPropertyName(property.property.Name);

                    if (property.kind == PropertyKind.Enum)
                    {
                        pb.IsEnum = true;
                    }

                    if (property.kind == PropertyKind.ChildContent)
                    {
                        pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString);
                    }

                    if (property.kind == PropertyKind.Delegate)
                    {
                        pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
                    }

                    xml = property.property.GetDocumentationCommentXml();
                    if (!string.IsNullOrEmpty(xml))
                    {
                        pb.Documentation = xml;
                    }
                });
            }

            var descriptor = builder.Build();

            return(descriptor);
        }