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 errorSink = new ParserErrorSink();
            var context = new CodeBuilderContext(generatorContext, errorSink);

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

            // Assert
            Assert.Equal(expectedValue, writer.GenerateCode());
        }
        /// <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);
        }
 /// <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="CodeGeneratorContext"/> 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>
 public virtual void RenderAttributeValue([NotNull] TagHelperAttributeDescriptor attributeDescriptor,
                                          [NotNull] CSharpCodeWriter writer,
                                          [NotNull] CodeBuilderContext context,
                                          [NotNull] Action <CSharpCodeWriter> renderAttributeValue)
 {
     renderAttributeValue(writer);
 }
        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);
        }
        // Internal for testing.
        internal static bool ValidateTagHelperAttributeDescriptor(
            TagHelperAttributeDescriptor attributeDescriptor,
            Type 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));

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

            return ValidateTagHelperAttributeNameOrPrefix(
                attributeDescriptor.Name,
                parentType,
                attributeDescriptor.PropertyName,
                errorSink,
                nameOrPrefix);
        }
            public override void RenderAttributeValue(
                TagHelperAttributeDescriptor attributeInfo,
                CSharpCodeWriter writer,
                CodeBuilderContext context,
                Action<CSharpCodeWriter> renderAttributeValue,
                bool complexValue)
            {
                writer.Write("**From custom attribute code renderer**: ");

                base.RenderAttributeValue(attributeInfo, writer, context, renderAttributeValue, complexValue);
            }
 // Internal for testing.
 internal static bool ValidateTagHelperAttributeDescriptor(
     TagHelperAttributeDescriptor attributeDescriptor,
     Type parentType,
     ErrorSink errorSink)
 {
     var nameOrPrefix = attributeDescriptor.IsIndexer ?
         Resources.TagHelperDescriptorFactory_Prefix :
         Resources.TagHelperDescriptorFactory_Name;
     return ValidateTagHelperAttributeNameOrPrefix(
         attributeDescriptor.Name,
         parentType,
         attributeDescriptor.PropertyName,
         errorSink,
         nameOrPrefix);
 }
 private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
                                   Action<CSharpCodeWriter> valueRenderer,
                                   bool complexValue)
 {
     AttributeValueCodeRenderer.RenderAttributeValue(
         attributeDescriptor,
         _writer,
         _context,
         valueRenderer,
         complexValue);
 }
 private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
         {
             writer.WriteStringLiteral(value);
         },
         complexValue: false);
 }
 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 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);
 }
        // 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 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;
            }
        }