Example #1
0
        public void RenderAttributeValue_RendersModelExpressionsCorrectly(string modelExpressionType,
                                                                          string propertyType,
                                                                          string expectedValue)
        {
            // Arrange
            var renderer = new MvcTagHelperAttributeValueCodeRenderer(
                new GeneratedTagHelperAttributeContext
            {
                ModelExpressionTypeName         = modelExpressionType,
                CreateModelExpressionMethodName = "SomeMethod"
            });
            var attributeDescriptor = new TagHelperAttributeDescriptor("MyAttribute", "SomeProperty", propertyType);
            var writer           = new CSharpCodeWriter();
            var generatorContext = new CodeGeneratorContext(host: null,
                                                            className: string.Empty,
                                                            rootNamespace: string.Empty,
                                                            sourceFile: string.Empty,
                                                            shouldGenerateLinePragmas: true);
            var context = new CodeBuilderContext(generatorContext);

            // Act
            renderer.RenderAttributeValue(attributeDescriptor, writer, context,
                                          (codeWriter) =>
            {
                codeWriter.Write("MyValue");
            });

            // Assert
            Assert.Equal(expectedValue, writer.GenerateCode());
        }
Example #2
0
 private static bool IsStringAttribute(TagHelperAttributeDescriptor attributeDescriptor)
 {
     return(string.Equals(
                attributeDescriptor.TypeName,
                typeof(string).FullName,
                StringComparison.Ordinal));
 }
        private TagHelperAttributeDescriptor GetIndexerAttributeDescriptor(ParameterInfo parameter, string name)
        {
            var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
                parameter.ParameterType,
                typeof(IDictionary <,>))
                                          ?.GenericTypeArguments
                                          .Select(t => t.IsGenericParameter ? null : t)
                                          .ToArray();

            if (dictionaryTypeArguments?[0] != typeof(string))
            {
                return(null);
            }

            var type       = dictionaryTypeArguments[1];
            var descriptor = new TagHelperAttributeDescriptor
            {
                Name         = name + "-",
                PropertyName = parameter.Name,
                TypeName     = GetCSharpTypeName(type),
                IsEnum       = type.GetTypeInfo().IsEnum,
                IsIndexer    = true
            };

            return(descriptor);
        }
        // Internal for testing.
        internal static bool ValidateTagHelperAttributeDescriptor(
            TagHelperAttributeDescriptor attributeDescriptor,
            ITypeInfo parentType,
            ErrorSink errorSink)
        {
            string nameOrPrefix;

            if (attributeDescriptor.IsIndexer)
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
            }
            else if (string.IsNullOrEmpty(attributeDescriptor.Name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
                        parentType.FullName,
                        attributeDescriptor.PropertyName),
                    length: 0);

                return(false);
            }
            else
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
            }

            return(ValidateTagHelperAttributeNameOrPrefix(
                       attributeDescriptor.Name,
                       parentType,
                       attributeDescriptor.PropertyName,
                       errorSink,
                       nameOrPrefix));
        }
        /// <inheritdoc />
        /// <remarks>If the attribute being rendered is of the type
        /// <see cref="GeneratedTagHelperAttributeContext.ModelExpressionTypeName"/>, then a model expression will be
        /// created by calling into <see cref="GeneratedTagHelperAttributeContext.CreateModelExpressionMethodName"/>.
        /// </remarks>
        public override void RenderAttributeValue([NotNull] TagHelperAttributeDescriptor attributeDescriptor,
                                                  [NotNull] CSharpCodeWriter writer,
                                                  [NotNull] CodeBuilderContext codeBuilderContext,
                                                  [NotNull] Action<CSharpCodeWriter> renderAttributeValue,
                                                  bool complexValue)
        {
            if (attributeDescriptor.TypeName.Equals(_context.ModelExpressionTypeName, StringComparison.Ordinal))
            {
                writer
                    .WriteStartMethodInvocation(_context.CreateModelExpressionMethodName)
                    .Write(ModelLambdaVariableName)
                    .Write(" => ");
                if (!complexValue)
                {
                    writer
                        .Write(ModelLambdaVariableName)
                        .Write(".");

                }

                renderAttributeValue(writer);

                writer.WriteEndMethodInvocation(endLine: false);
            }
            else
            {
                base.RenderAttributeValue(
                    attributeDescriptor,
                    writer,
                    codeBuilderContext,
                    renderAttributeValue,
                    complexValue);
            }
        }
        /// <summary>
        /// Called during Razor's code generation process to generate code that instantiates the value of the tag
        /// helper's property. Last value written should not be or end with a semicolon.
        /// </summary>
        /// <param name="attributeDescriptor">
        /// The <see cref="TagHelperAttributeDescriptor"/> to generate code for.
        /// </param>
        /// <param name="writer">The <see cref="CSharpCodeWriter"/> that's used to write code.</param>
        /// <param name="context">A <see cref="Chunks.Generators.ChunkGeneratorContext"/> instance that contains
        /// information about the current code generation process.</param>
        /// <param name="renderAttributeValue">
        /// <see cref="Action"/> that renders the raw value of the HTML attribute.
        /// </param>
        /// <param name="complexValue">
        /// Indicates whether or not the source attribute value contains more than simple text. <c>false</c> for plain
        /// C# expressions e.g. <c>"PropertyName"</c>. <c>true</c> if the attribute value contain at least one in-line
        /// Razor construct e.g. <c>"@(@readonly)"</c>.
        /// </param>
        public virtual void RenderAttributeValue(
            TagHelperAttributeDescriptor attributeDescriptor,
            CSharpCodeWriter writer,
            CodeGeneratorContext context,
            Action <CSharpCodeWriter> renderAttributeValue,
            bool complexValue)
        {
            if (attributeDescriptor == null)
            {
                throw new ArgumentNullException(nameof(attributeDescriptor));
            }

            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (renderAttributeValue == null)
            {
                throw new ArgumentNullException(nameof(renderAttributeValue));
            }

            renderAttributeValue(writer);
        }
 private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
     {
         RenderBufferedAttributeValueAccessor(writer);
     });
 }
 /// <summary>
 /// Called during Razor's code generation process to generate code that instantiates the value of the tag
 /// helper's property. Last value written should not be or end with a semicolon.
 /// </summary>
 /// <param name="attributeDescriptor">
 /// The <see cref="TagHelperAttributeDescriptor"/> to generate code for.
 /// </param>
 /// <param name="writer">The <see cref="CSharpCodeWriter"/> that's used to write code.</param>
 /// <param name="context">A <see cref="Chunks.Generators.ChunkGeneratorContext"/> instance that contains
 /// information about the current code generation process.</param>
 /// <param name="renderAttributeValue">
 /// <see cref="Action"/> that renders the raw value of the HTML attribute.
 /// </param>
 /// <param name="complexValue">
 /// Indicates whether or not the source attribute value contains more than simple text. <c>false</c> for plain
 /// C# expressions e.g. <c>"PropertyName"</c>. <c>true</c> if the attribute value contain at least one in-line
 /// Razor construct e.g. <c>"@(@readonly)"</c>.
 /// </param>
 public virtual void RenderAttributeValue(
     [NotNull] TagHelperAttributeDescriptor attributeDescriptor,
     [NotNull] CSharpCodeWriter writer,
     [NotNull] CodeGeneratorContext context,
     [NotNull] Action <CSharpCodeWriter> renderAttributeValue,
     bool complexValue)
 {
     renderAttributeValue(writer);
 }
 private void RenderRawAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
     {
         writer.Write(value);
     });
 }
            public override void RenderAttributeValue([NotNull] TagHelperAttributeDescriptor attributeInfo,
                                                      [NotNull] CSharpCodeWriter writer,
                                                      [NotNull] CodeBuilderContext context,
                                                      [NotNull] Action <CSharpCodeWriter> renderAttributeValue)
            {
                writer.Write("**From custom attribute code renderer**: ");

                base.RenderAttributeValue(attributeInfo, writer, context, renderAttributeValue);
            }
 private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
     {
         writer.WriteStringLiteral(value);
     });
 }
        /// <inheritdoc />
        /// <remarks>If the attribute being rendered is of the type
        /// <see cref="GeneratedTagHelperAttributeContext.ModelExpressionTypeName"/>, then a model expression will be
        /// created by calling into <see cref="GeneratedTagHelperAttributeContext.CreateModelExpressionMethodName"/>.
        /// </remarks>
        public override void RenderAttributeValue(
            TagHelperAttributeDescriptor attributeDescriptor,
            CSharpCodeWriter writer,
            CodeGeneratorContext codeGeneratorContext,
            Action <CSharpCodeWriter> renderAttributeValue,
            bool complexValue)
        {
            if (attributeDescriptor == null)
            {
                throw new ArgumentNullException(nameof(attributeDescriptor));
            }

            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (codeGeneratorContext == null)
            {
                throw new ArgumentNullException(nameof(codeGeneratorContext));
            }

            if (renderAttributeValue == null)
            {
                throw new ArgumentNullException(nameof(renderAttributeValue));
            }

            if (attributeDescriptor.TypeName.Equals(_context.ModelExpressionTypeName, StringComparison.Ordinal))
            {
                writer
                .WriteStartInstanceMethodInvocation(_context.ModelExpressionProviderPropertyName, _context.CreateModelExpressionMethodName)
                .Write(_context.ViewDataPropertyName)
                .WriteParameterSeparator()
                .Write(ModelLambdaVariableName)
                .Write(" => ");
                if (!complexValue)
                {
                    writer
                    .Write(ModelLambdaVariableName)
                    .Write(".");
                }

                renderAttributeValue(writer);

                writer.WriteEndMethodInvocation(endLine: false);
            }
            else
            {
                base.RenderAttributeValue(
                    attributeDescriptor,
                    writer,
                    codeGeneratorContext,
                    renderAttributeValue,
                    complexValue);
            }
        }
Example #13
0
            public override void RenderAttributeValue(
                TagHelperAttributeDescriptor attributeInfo,
                CSharpCodeWriter writer,
                CodeGeneratorContext context,
                Action <CSharpCodeWriter> renderAttributeValue,
                bool complexValue)
            {
                writer.Write("**From custom attribute code renderer**: ");

                base.RenderAttributeValue(attributeInfo, writer, context, renderAttributeValue, complexValue);
            }
Example #14
0
 private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
 {
     // Pass complexValue: false because variable.ToString() replaces any original complexity in the expression.
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
     {
         RenderBufferedAttributeValueAccessor(writer);
     },
         complexValue: false);
 }
Example #15
0
 private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
                                   Action <CSharpCodeWriter> valueRenderer,
                                   bool complexValue)
 {
     AttributeValueCodeRenderer.RenderAttributeValue(
         attributeDescriptor,
         _writer,
         _context,
         valueRenderer,
         complexValue);
 }
        private static string GetTagHelperPropertyAccessor(
            string tagHelperVariableName,
            string attributeName,
            TagHelperAttributeDescriptor associatedDescriptor)
        {
            var propertyAccessor = $"{tagHelperVariableName}.{associatedDescriptor.PropertyName}";

            if (associatedDescriptor.IsIndexer)
            {
                var dictionaryKey = attributeName.Substring(associatedDescriptor.Name.Length);
                propertyAccessor += $"[\"{dictionaryKey}\"]";
            }

            return(propertyAccessor);
        }
Example #17
0
 private void RenderRawAttributeValue(
     Chunk attributeValueChunk,
     TagHelperAttributeDescriptor attributeDescriptor,
     bool isPlainTextValue)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
     {
         var visitor =
             new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
         visitor.Accept(attributeValueChunk);
     },
         complexValue: !isPlainTextValue);
 }
Example #18
0
        private static TagHelperChunk GetViewComponentTagHelperChunk(string name, bool visitedTagHelperChunks)
        {
            var typeName = visitedTagHelperChunks ? $"{_testNamespace}.{_testClass}.{name}Type" : $"{name}Type";

            var attribute = new TagHelperAttributeDescriptor
            {
                Name         = "attribute",
                PropertyName = "Attribute",
                TypeName     = typeof(string).FullName
            };

            var requiredAttribute = new TagHelperRequiredAttributeDescriptor
            {
                Name = "Attribute"
            };

            var tagHelperDescriptor = new TagHelperDescriptor
            {
                AssemblyName = $"{name}Assembly",
                TagName      = name.ToLowerInvariant(),
                TypeName     = typeName,
                Attributes   = new[]
                {
                    attribute
                },
                RequiredAttributes = new[]
                {
                    requiredAttribute
                }
            };

            tagHelperDescriptor.PropertyBag.Add(
                ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey,
                name);

            var tagHelperChunk = new TagHelperChunk(
                $"vc:{name.ToLowerInvariant()}",
                TagMode.SelfClosing,
                new List <TagHelperAttributeTracker>(),
                new[]
            {
                tagHelperDescriptor
            });

            return(tagHelperChunk);
        }
        public void TagHelperAttributeDescriptor_IsStringPropertySetCorrectly(
            Type attributeType,
            bool isIndexer,
            bool expectedIsStringProperty)
        {
            // Arrange
            var attributeDescriptor = new TagHelperAttributeDescriptor
            {
                Name         = "someAttribute",
                PropertyName = "someProperty",
                TypeName     = attributeType.FullName,
                IsIndexer    = isIndexer
            };

            // Assert
            Assert.Equal(expectedIsStringProperty, attributeDescriptor.IsStringProperty);
        }
        private void SetAttributeDescriptors(ViewComponentDescriptor viewComponentDescriptor,
                                             TagHelperDescriptor tagHelperDescriptor)
        {
            var methodParameters             = viewComponentDescriptor.MethodInfo.GetParameters();
            var attributeDescriptors         = new List <TagHelperAttributeDescriptor>();
            var indexerDescriptors           = new List <TagHelperAttributeDescriptor>();
            var requiredAttributeDescriptors = new List <TagHelperRequiredAttributeDescriptor>();

            foreach (var parameter in methodParameters)
            {
                var lowerKebabName = TagHelperDescriptorFactory.ToHtmlCase(parameter.Name);
                var typeName       = GetCSharpTypeName(parameter.ParameterType);
                var descriptor     = new TagHelperAttributeDescriptor
                {
                    Name         = lowerKebabName,
                    PropertyName = parameter.Name,
                    TypeName     = typeName
                };

                descriptor.IsEnum    = parameter.ParameterType.GetTypeInfo().IsEnum;
                descriptor.IsIndexer = false;

                attributeDescriptors.Add(descriptor);

                var indexerDescriptor = GetIndexerAttributeDescriptor(parameter, lowerKebabName);
                if (indexerDescriptor != null)
                {
                    indexerDescriptors.Add(indexerDescriptor);
                }
                else
                {
                    // Set required attributes only for non-indexer attributes. Indexer attributes can't be required attributes
                    // because there are two ways of setting values for the attribute.
                    requiredAttributeDescriptors.Add(new TagHelperRequiredAttributeDescriptor
                    {
                        Name = lowerKebabName
                    });
                }
            }

            attributeDescriptors.AddRange(indexerDescriptors);
            tagHelperDescriptor.Attributes         = attributeDescriptors;
            tagHelperDescriptor.RequiredAttributes = requiredAttributeDescriptors;
        }
Example #21
0
        public void RenderAttributeValue_RendersModelExpressionsCorrectly(
            string modelExpressionType,
            string propertyType,
            string expectedValue)
        {
            // Arrange
            var renderer = new MvcTagHelperAttributeValueCodeRenderer(
                new GeneratedTagHelperAttributeContext
            {
                ModelExpressionTypeName             = modelExpressionType,
                CreateModelExpressionMethodName     = "SomeMethod",
                ModelExpressionProviderPropertyName = "Provider",
                ViewDataPropertyName = "ViewData"
            });
            var attributeDescriptor = new TagHelperAttributeDescriptor
            {
                Name         = "MyAttribute",
                PropertyName = "SomeProperty",
                TypeName     = propertyType,
            };
            var writer           = new CSharpCodeWriter();
            var generatorContext = new ChunkGeneratorContext(
                host: null,
                className: string.Empty,
                rootNamespace: string.Empty,
                sourceFile: string.Empty,
                shouldGenerateLinePragmas: true);
            var errorSink = new ErrorSink();
            var context   = new CodeGeneratorContext(generatorContext, errorSink);

            // Act
            renderer.RenderAttributeValue(attributeDescriptor, writer, context,
                                          (codeWriter) =>
            {
                codeWriter.Write("MyValue");
            },
                                          complexValue: false);

            // Assert
            Assert.Equal(expectedValue, writer.GenerateCode());
        }
        private void RenderCodeAttributeValue(
            Chunk attributeValueChunk,
            TagHelperAttributeDescriptor attributeDescriptor,
            bool isPlainTextValue)
        {
            RenderAttributeValue(
                attributeDescriptor,
                valueRenderer: (writer) =>
            {
                if (attributeDescriptor.IsEnum && isPlainTextValue)
                {
                    writer
                    .Write("global::")
                    .Write(attributeDescriptor.TypeName)
                    .Write(".");
                }

                var visitor =
                    new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
                visitor.Accept(attributeValueChunk);
            },
                complexValue: !isPlainTextValue);
        }
 private static bool IsStringAttribute(TagHelperAttributeDescriptor attributeDescriptor)
 {
     return(attributeDescriptor.PropertyInfo.PropertyType == typeof(string));
 }
Example #24
0
        // Render assignment of attribute value to the value accessor.
        private void RenderNewAttributeValueAssignment(
            TagHelperAttributeDescriptor attributeDescriptor,
            bool bufferableAttribute,
            Chunk attributeValueChunk,
            string valueAccessor)
        {
            // Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
            // may be more than just a plain text value, it may also contain Razor code which is why we attempt
            // to retrieve a plain text value here.
            string textValue;
            var    isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);

            if (bufferableAttribute)
            {
                if (!isPlainTextValue)
                {
                    // If we haven't recorded a value and we need to buffer an attribute value and the value is not
                    // plain text then we need to prepare the value prior to setting it below.
                    BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: false);
                }

                _writer.WriteStartAssignment(valueAccessor);

                if (isPlainTextValue)
                {
                    // If the attribute is bufferable but has a plain text value that means the value
                    // is a string which needs to be surrounded in quotes.
                    RenderQuotedAttributeValue(textValue, attributeDescriptor);
                }
                else
                {
                    // The value contains more than plain text e.g. stringAttribute ="Time: @DateTime.Now".
                    RenderBufferedAttributeValue(attributeDescriptor);
                }

                _writer.WriteLine(";");
            }
            else
            {
                // Write out simple assignment for non-string property value. Try to keep the whole
                // statement together and the #line pragma correct to make debugging possible.
                using (var lineMapper = new CSharpLineMappingWriter(
                           _writer,
                           attributeValueChunk.Association.Start,
                           _context.SourceFile))
                {
                    // Place the assignment LHS to align RHS with original attribute value's indentation.
                    // Unfortunately originalIndent is incorrect if original line contains tabs. Unable to
                    // use a CSharpPaddingBuilder because the Association has no Previous node; lost the
                    // original Span sequence when the parse tree was rewritten.
                    var originalIndent  = attributeValueChunk.Start.CharacterIndex;
                    var generatedLength = valueAccessor.Length + " = ".Length;
                    var newIndent       = originalIndent - generatedLength;
                    if (newIndent > 0)
                    {
                        _writer.Indent(newIndent);
                    }

                    _writer.WriteStartAssignment(valueAccessor);
                    lineMapper.MarkLineMappingStart();

                    // Write out bare expression for this attribute value. Property is not a string.
                    // So quoting or buffering are not helpful.
                    RenderRawAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue);

                    // End the assignment to the attribute.
                    lineMapper.MarkLineMappingEnd();
                    _writer.WriteLine(";");
                }
            }
        }
Example #25
0
        private string RenderBoundAttribute(
            string attributeName,
            Chunk attributeValueChunk,
            string tagHelperVariableName,
            string previousValueAccessor,
            TagHelperAttributeDescriptor attributeDescriptor)
        {
            var currentValueAccessor = string.Format(
                CultureInfo.InvariantCulture,
                "{0}.{1}",
                tagHelperVariableName,
                attributeDescriptor.PropertyName);

            if (attributeDescriptor.IsIndexer)
            {
                var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
                currentValueAccessor += $"[\"{dictionaryKey}\"]";
            }

            // If this attribute value has not been seen before, need to record its value.
            if (previousValueAccessor == null)
            {
                // Bufferable attributes are attributes that can have Razor code inside of them. Such
                // attributes have string values and may be calculated using a temporary TextWriter or other
                // buffer.
                var bufferableAttribute = attributeDescriptor.IsStringProperty;

                RenderNewAttributeValueAssignment(
                    attributeDescriptor,
                    bufferableAttribute,
                    attributeValueChunk,
                    currentValueAccessor);

                if (_designTimeMode)
                {
                    // Execution contexts are a runtime feature.
                    return(currentValueAccessor);
                }

                // We need to inform the context of the attribute value.
                _writer
                .WriteStartInstanceMethodInvocation(
                    ExecutionContextVariableName,
                    _tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
                .WriteStringLiteral(attributeName)
                .WriteParameterSeparator()
                .Write(currentValueAccessor)
                .WriteEndMethodInvocation();

                return(currentValueAccessor);
            }
            else
            {
                // The attribute value has already been determined and accessor was passed to us as
                // previousValueAccessor, we don't want to evaluate the value twice so lets just use the
                // previousValueLocation.
                _writer
                .WriteStartAssignment(currentValueAccessor)
                .Write(previousValueAccessor)
                .WriteLine(";");

                return(previousValueAccessor);
            }
        }
        public void ValidateTagHelperAttributeDescriptor_WithValidPrefix_ReturnsTrue(string prefix)
        {
            // Arrange
            var descriptor = new TagHelperAttributeDescriptor
            {
                Name = prefix,
                PropertyName = "ValidProperty",
                TypeName = "PropertyType",
                IsIndexer = true
            };
            var errorSink = new ErrorSink();

            // Act
            var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
                descriptor,
                GetTypeInfo(typeof(MultiTagTagHelper)),
                errorSink);

            // Assert
            Assert.True(result);
            Assert.Empty(errorSink.Errors);
        }
        public void ValidateTagHelperAttributeDescriptor_WithInvalidPrefix_AddsExpectedErrors(
            string prefix,
            string[] expectedErrorMessages)
        {
            // Arrange
            var descriptor = new TagHelperAttributeDescriptor
            {
                Name = prefix,
                PropertyName = "InvalidProperty",
                TypeName = "ValuesType",
                IsIndexer = true
            };
            var errorSink = new ErrorSink();

            // Act
            var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
                descriptor,
                GetTypeInfo(typeof(MultiTagTagHelper)),
                errorSink);

            // Assert
            Assert.False(result);

            var errors = errorSink.Errors.ToArray();
            Assert.Equal(expectedErrorMessages.Length, errors.Length);
            for (var i = 0; i < expectedErrorMessages.Length; i++)
            {
                Assert.Equal(0, errors[i].Length);
                Assert.Equal(SourceLocation.Zero, errors[i].Location);
                Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal);
            }
        }
            public override void RenderAttributeValue(
                TagHelperAttributeDescriptor attributeInfo,
                CSharpCodeWriter writer,
                CodeGeneratorContext context,
                Action<CSharpCodeWriter> renderAttributeValue,
                bool complexValue)
            {
                writer.Write("**From custom attribute code renderer**: ");

                base.RenderAttributeValue(attributeInfo, writer, context, renderAttributeValue, complexValue);
            }
        private string RenderBoundAttribute(
            string attributeName,
            Chunk attributeValueChunk,
            string tagHelperVariableName,
            string previousValueAccessor,
            TagHelperAttributeDescriptor attributeDescriptor)
        {
            var currentValueAccessor = string.Format(
                CultureInfo.InvariantCulture,
                "{0}.{1}",
                tagHelperVariableName,
                attributeDescriptor.PropertyName);

            if (attributeDescriptor.IsIndexer)
            {
                var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
                currentValueAccessor += $"[\"{dictionaryKey}\"]";
            }

            // If this attribute value has not been seen before, need to record its value.
            if (previousValueAccessor == null)
            {
                // Bufferable attributes are attributes that can have Razor code inside of them. Such
                // attributes have string values and may be calculated using a temporary TextWriter or other
                // buffer.
                var bufferableAttribute = attributeDescriptor.IsStringProperty;

                RenderNewAttributeValueAssignment(
                    attributeDescriptor,
                    bufferableAttribute,
                    attributeValueChunk,
                    currentValueAccessor);

                if (_designTimeMode)
                {
                    // Execution contexts are a runtime feature.
                    return currentValueAccessor;
                }

                // We need to inform the context of the attribute value.
                _writer
                    .WriteStartInstanceMethodInvocation(
                        ExecutionContextVariableName,
                        _tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
                    .WriteStringLiteral(attributeName)
                    .WriteParameterSeparator()
                    .Write(currentValueAccessor)
                    .WriteEndMethodInvocation();

                return currentValueAccessor;
            }
            else
            {
                // The attribute value has already been determined and accessor was passed to us as
                // previousValueAccessor, we don't want to evaluate the value twice so lets just use the
                // previousValueLocation.
                _writer
                    .WriteStartAssignment(currentValueAccessor)
                    .Write(previousValueAccessor)
                    .WriteLine(";");

                return previousValueAccessor;
            }
        }
        // Render assignment of attribute value to the value accessor.
        private void RenderNewAttributeValueAssignment(
            TagHelperAttributeDescriptor attributeDescriptor,
            bool bufferableAttribute,
            Chunk attributeValueChunk,
            string valueAccessor)
        {
            // Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
            // may be more than just a plain text value, it may also contain Razor code which is why we attempt
            // to retrieve a plain text value here.
            string textValue;
            var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);

            if (bufferableAttribute)
            {
                if (!isPlainTextValue)
                {
                    // If we haven't recorded a value and we need to buffer an attribute value and the value is not
                    // plain text then we need to prepare the value prior to setting it below.
                    BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: false);
                }

                _writer.WriteStartAssignment(valueAccessor);

                if (isPlainTextValue)
                {
                    // If the attribute is bufferable but has a plain text value that means the value
                    // is a string which needs to be surrounded in quotes.
                    RenderQuotedAttributeValue(textValue, attributeDescriptor);
                }
                else
                {
                    // The value contains more than plain text e.g. stringAttribute ="Time: @DateTime.Now".
                    RenderBufferedAttributeValue(attributeDescriptor);
                }

                _writer.WriteLine(";");
            }
            else
            {
                // Write out simple assignment for non-string property value. Try to keep the whole
                // statement together and the #line pragma correct to make debugging possible.
                using (var lineMapper = new CSharpLineMappingWriter(
                    _writer,
                    attributeValueChunk.Association.Start,
                    _context.SourceFile))
                {
                    // Place the assignment LHS to align RHS with original attribute value's indentation.
                    // Unfortunately originalIndent is incorrect if original line contains tabs. Unable to
                    // use a CSharpPaddingBuilder because the Association has no Previous node; lost the
                    // original Span sequence when the parse tree was rewritten.
                    var originalIndent = attributeValueChunk.Start.CharacterIndex;
                    var generatedLength = valueAccessor.Length + " = ".Length;
                    var newIndent = originalIndent - generatedLength;
                    if (newIndent > 0)
                    {
                        _writer.Indent(newIndent);
                    }

                    _writer.WriteStartAssignment(valueAccessor);
                    lineMapper.MarkLineMappingStart();

                    // Write out bare expression for this attribute value. Property is not a string.
                    // So quoting or buffering are not helpful.
                    RenderRawAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue);

                    // End the assignment to the attribute.
                    lineMapper.MarkLineMappingEnd();
                    _writer.WriteLine(";");
                }
            }
        }
 private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
                                   Action<CSharpCodeWriter> valueRenderer,
                                   bool complexValue)
 {
     AttributeValueCodeRenderer.RenderAttributeValue(
         attributeDescriptor,
         _writer,
         _context,
         valueRenderer,
         complexValue);
 }
 private void RenderRawAttributeValue(
     Chunk attributeValueChunk,
     TagHelperAttributeDescriptor attributeDescriptor,
     bool isPlainTextValue)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
         {
             var visitor =
                 new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
             visitor.Accept(attributeValueChunk);
         },
         complexValue: !isPlainTextValue);
 }
        private static IEnumerable <TagHelperAttributeDescriptor> GetAttributeDescriptors(
            ITypeInfo type,
            bool designTime,
            ErrorSink errorSink)
        {
            var attributeDescriptors = new List <TagHelperAttributeDescriptor>();

            // Keep indexer descriptors separate to avoid sorting the combined list later.
            var indexerDescriptors = new List <TagHelperAttributeDescriptor>();

            var accessibleProperties = type.Properties.Where(IsAccessibleProperty);

            foreach (var property in accessibleProperties)
            {
                if (ShouldSkipDescriptorCreation(designTime, property))
                {
                    continue;
                }

                var attributeNameAttribute = property
                                             .GetCustomAttributes <HtmlAttributeNameAttribute>()
                                             .FirstOrDefault();
                var hasExplicitName =
                    attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
                var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);

                TagHelperAttributeDescriptor mainDescriptor = null;
                if (property.HasPublicSetter)
                {
                    mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
                    if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
                    {
                        // HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely.
                        continue;
                    }
                }
                else if (hasExplicitName)
                {
                    // Specified HtmlAttributeNameAttribute.Name though property has no public setter.
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(
                            type.FullName,
                            property.Name,
                            typeof(HtmlAttributeNameAttribute).FullName,
                            nameof(HtmlAttributeNameAttribute.Name)),
                        length: 0);
                    continue;
                }

                bool isInvalid;
                var  indexerDescriptor = ToIndexerAttributeDescriptor(
                    property,
                    attributeNameAttribute,
                    parentType: type,
                    errorSink: errorSink,
                    defaultPrefix: attributeName + "-",
                    designTime: designTime,
                    isInvalid: out isInvalid);
                if (indexerDescriptor != null &&
                    !ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
                {
                    isInvalid = true;
                }

                if (isInvalid)
                {
                    // The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the
                    // HTML-casing of the property name) is invalid. Ignore this property completely.
                    continue;
                }

                if (mainDescriptor != null)
                {
                    attributeDescriptors.Add(mainDescriptor);
                }

                if (indexerDescriptor != null)
                {
                    indexerDescriptors.Add(indexerDescriptor);
                }
            }

            attributeDescriptors.AddRange(indexerDescriptors);

            return(attributeDescriptors);
        }
        // Internal for testing.
        internal static bool ValidateTagHelperAttributeDescriptor(
            TagHelperAttributeDescriptor attributeDescriptor,
            ITypeInfo parentType,
            ErrorSink errorSink)
        {
            string nameOrPrefix;
            if (attributeDescriptor.IsIndexer)
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
            }
            else if (string.IsNullOrEmpty(attributeDescriptor.Name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
                        parentType.FullName,
                        attributeDescriptor.PropertyName),
                    length: 0);

                return false;
            }
            else
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
            }

            return ValidateTagHelperAttributeNameOrPrefix(
                attributeDescriptor.Name,
                parentType,
                attributeDescriptor.PropertyName,
                errorSink,
                nameOrPrefix);
        }
 private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
 {
     // Pass complexValue: false because variable.ToString() replaces any original complexity in the expression.
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
         {
             RenderBufferedAttributeValueAccessor(writer);
         },
         complexValue: false);
 }
 private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
         {
             writer.WriteStringLiteral(value);
         },
         complexValue: false);
 }