Ejemplo n.º 1
0
        private TagHelperDescriptor CreateChildContentDescriptor(ComponentSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
        {
            var typeName     = component.GetTypeName() + "." + attribute.Name;
            var assemblyName = component.AssemblyName;

            var builder = TagHelperDescriptorBuilder.Create(ComponentMetadata.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] = ComponentMetadata.ChildContent.RuntimeName;

            // Opt out of processing as a component. We'll process this specially as part of the component's body.
            builder.Metadata[ComponentMetadata.SpecialKindKey] = ComponentMetadata.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.
                CreateContextParameter(builder, attribute.Name);
            }

            if (component.IsComponentFullyQualifiedNameMatch())
            {
                builder.Metadata[ComponentMetadata.Component.NameMatchKey] = ComponentMetadata.Component.FullyQualifiedNameMatch;
            }

            var descriptor = builder.Build();

            return(descriptor);
        }
 public static BoundAttributeDescriptionInfo From(BoundAttributeDescriptor boundAttribute, bool indexer) => From(boundAttribute, indexer, parentTagHelperTypeName: null);
Ejemplo n.º 3
0
        private List <TagHelperDescriptor> CreateComponentBindTagHelpers(ICollection <TagHelperDescriptor> tagHelpers)
        {
            var results = new List <TagHelperDescriptor>();

            foreach (var tagHelper in tagHelpers)
            {
                if (!tagHelper.IsComponentTagHelper())
                {
                    continue;
                }

                // We want to create a 'bind' tag helper everywhere we see a pair of properties like `Foo`, `FooChanged`
                // where `FooChanged` is a delegate and `Foo` is not.
                //
                // The easiest way to figure this out without a lot of backtracking is to look for `FooChanged` and then
                // try to find a matching "Foo".
                for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
                {
                    var changeAttribute = tagHelper.BoundAttributes[i];
                    if (!changeAttribute.Name.EndsWith("Changed") || !changeAttribute.IsDelegateProperty())
                    {
                        continue;
                    }

                    BoundAttributeDescriptor valueAttribute = null;
                    var valueAttributeName = changeAttribute.Name.Substring(0, changeAttribute.Name.Length - "Changed".Length);
                    for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
                    {
                        if (tagHelper.BoundAttributes[j].Name == valueAttributeName && !tagHelper.BoundAttributes[j].IsDelegateProperty())
                        {
                            valueAttribute = tagHelper.BoundAttributes[j];
                            break;
                        }
                    }

                    if (valueAttribute == null)
                    {
                        // No matching attribute found.
                        continue;
                    }

                    var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Bind.TagHelperKind, tagHelper.Name, tagHelper.AssemblyName);
                    builder.DisplayName   = tagHelper.DisplayName;
                    builder.Documentation = string.Format(
                        ComponentResources.BindTagHelper_Component_Documentation,
                        valueAttribute.Name,
                        changeAttribute.Name);

                    builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Bind.TagHelperKind);
                    builder.Metadata[TagHelperMetadata.Runtime.Name]      = BlazorMetadata.Bind.RuntimeName;
                    builder.Metadata[BlazorMetadata.Bind.ValueAttribute]  = valueAttribute.Name;
                    builder.Metadata[BlazorMetadata.Bind.ChangeAttribute] = changeAttribute.Name;

                    // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
                    // a C# property will crash trying to create the toolips.
                    builder.SetTypeName(tagHelper.GetTypeName());

                    // Match the component and attribute name
                    builder.TagMatchingRule(rule =>
                    {
                        rule.TagName = tagHelper.TagMatchingRules.Single().TagName;
                        rule.Attribute(attribute =>
                        {
                            attribute.Name = "bind-" + valueAttribute.Name;
                            attribute.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
                        });
                    });

                    builder.BindAttribute(attribute =>
                    {
                        attribute.Documentation = string.Format(
                            ComponentResources.BindTagHelper_Component_Documentation,
                            valueAttribute.Name,
                            changeAttribute.Name);

                        attribute.Name     = "bind-" + valueAttribute.Name;
                        attribute.TypeName = valueAttribute.TypeName;
                        attribute.IsEnum   = valueAttribute.IsEnum;

                        // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
                        // a C# property will crash trying to create the toolips.
                        attribute.SetPropertyName(valueAttribute.GetPropertyName());
                    });

                    results.Add(builder.Build());
                }
            }

            return(results);
        }
Ejemplo n.º 4
0
        // Attempts to compute the attribute names that should be used for an instance of 'bind'.
        private bool TryComputeAttributeNames(
            IntermediateNode parent,
            TagHelperPropertyIntermediateNode node,
            string attributeName,
            out string valueAttributeName,
            out string changeAttributeName,
            out BoundAttributeDescriptor valueAttribute,
            out BoundAttributeDescriptor changeAttribute)
        {
            valueAttribute  = null;
            changeAttribute = null;

            // Even though some of our 'bind' tag helpers specify the attribute names, they
            // should still satisfy one of the valid syntaxes.
            if (!TryParseBindAttribute(attributeName, out valueAttributeName, out changeAttributeName))
            {
                return(false);
            }

            // The tag helper specifies attribute names, they should win.
            //
            // This handles cases like <input type="text" bind="@Foo" /> where the tag helper is
            // generated to match a specific tag and has metadata that identify the attributes.
            //
            // We expect 1 bind tag helper per-node.
            valueAttributeName  = node.TagHelper.GetValueAttributeName() ?? valueAttributeName;
            changeAttributeName = node.TagHelper.GetChangeAttributeName() ?? changeAttributeName;

            // We expect 0-1 components per-node.
            var componentTagHelper = (parent as ComponentExtensionNode)?.Component;

            if (componentTagHelper == null)
            {
                // If it's not a component node then there isn't too much else to figure out.
                return(attributeName != null && changeAttributeName != null);
            }

            // If this is a component, we need an attribute name for the value.
            if (attributeName == null)
            {
                return(false);
            }

            // If this is a component, then we can infer '<PropertyName>Changed' as the name
            // of the change event.
            if (changeAttributeName == null)
            {
                changeAttributeName = valueAttributeName + "Changed";
            }

            for (var i = 0; i < componentTagHelper.BoundAttributes.Count; i++)
            {
                var attribute = componentTagHelper.BoundAttributes[i];

                if (string.Equals(valueAttributeName, attribute.Name))
                {
                    valueAttribute = attribute;
                }

                if (string.Equals(changeAttributeName, attribute.Name))
                {
                    changeAttribute = attribute;
                }
            }

            return(true);
        }
Ejemplo n.º 5
0
        private List <TagHelperDescriptor> CreateComponentBindTagHelpers(ICollection <TagHelperDescriptor> tagHelpers)
        {
            var results = new List <TagHelperDescriptor>();

            foreach (var tagHelper in tagHelpers)
            {
                if (!tagHelper.IsComponentTagHelper())
                {
                    continue;
                }

                // We want to create a 'bind' tag helper everywhere we see a pair of properties like `Foo`, `FooChanged`
                // where `FooChanged` is a delegate and `Foo` is not.
                //
                // The easiest way to figure this out without a lot of backtracking is to look for `FooChanged` and then
                // try to find a matching "Foo".
                //
                // We also look for a corresponding FooExpression attribute, though its presence is optional.
                for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
                {
                    var changeAttribute = tagHelper.BoundAttributes[i];
                    if (!changeAttribute.Name.EndsWith("Changed") ||

                        // Allow the ValueChanged attribute to be a delegate or EventCallback<>.
                        //
                        // We assume that the Delegate or EventCallback<> has a matching type, and the C# compiler will help
                        // you figure figure it out if you did it wrongly.
                        (!changeAttribute.IsDelegateProperty() && !changeAttribute.IsEventCallbackProperty()))
                    {
                        continue;
                    }

                    BoundAttributeDescriptor valueAttribute      = null;
                    BoundAttributeDescriptor expressionAttribute = null;
                    var valueAttributeName      = changeAttribute.Name.Substring(0, changeAttribute.Name.Length - "Changed".Length);
                    var expressionAttributeName = valueAttributeName + "Expression";
                    for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
                    {
                        if (tagHelper.BoundAttributes[j].Name == valueAttributeName)
                        {
                            valueAttribute = tagHelper.BoundAttributes[j];
                        }

                        if (tagHelper.BoundAttributes[j].Name == expressionAttributeName)
                        {
                            expressionAttribute = tagHelper.BoundAttributes[j];
                        }

                        if (valueAttribute != null && expressionAttribute != null)
                        {
                            // We found both, so we can stop looking now
                            break;
                        }
                    }

                    if (valueAttribute == null)
                    {
                        // No matching attribute found.
                        continue;
                    }

                    var builder = TagHelperDescriptorBuilder.Create(ComponentMetadata.Bind.TagHelperKind, tagHelper.Name, tagHelper.AssemblyName);
                    builder.DisplayName   = tagHelper.DisplayName;
                    builder.Documentation = string.Format(
                        ComponentResources.BindTagHelper_Component_Documentation,
                        valueAttribute.Name,
                        changeAttribute.Name);

                    builder.Metadata.Add(ComponentMetadata.SpecialKindKey, ComponentMetadata.Bind.TagHelperKind);
                    builder.Metadata[TagHelperMetadata.Runtime.Name]         = ComponentMetadata.Bind.RuntimeName;
                    builder.Metadata[ComponentMetadata.Bind.ValueAttribute]  = valueAttribute.Name;
                    builder.Metadata[ComponentMetadata.Bind.ChangeAttribute] = changeAttribute.Name;

                    if (expressionAttribute != null)
                    {
                        builder.Metadata[ComponentMetadata.Bind.ExpressionAttribute] = expressionAttribute.Name;
                    }

                    // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
                    // a C# property will crash trying to create the toolips.
                    builder.SetTypeName(tagHelper.GetTypeName());

                    // Match the component and attribute name
                    builder.TagMatchingRule(rule =>
                    {
                        rule.TagName = tagHelper.TagMatchingRules.Single().TagName;
                        rule.Attribute(attribute =>
                        {
                            attribute.Name = "@bind-" + valueAttribute.Name;
                            attribute.NameComparisonMode = RequiredAttributeDescriptor.NameComparisonMode.FullMatch;
                            attribute.Metadata[ComponentMetadata.Common.DirectiveAttribute] = bool.TrueString;
                        });
                    });

                    builder.BindAttribute(attribute =>
                    {
                        attribute.Metadata[ComponentMetadata.Common.DirectiveAttribute] = bool.TrueString;
                        attribute.Documentation = string.Format(
                            ComponentResources.BindTagHelper_Component_Documentation,
                            valueAttribute.Name,
                            changeAttribute.Name);

                        attribute.Name     = "@bind-" + valueAttribute.Name;
                        attribute.TypeName = changeAttribute.TypeName;
                        attribute.IsEnum   = valueAttribute.IsEnum;

                        // WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
                        // a C# property will crash trying to create the toolips.
                        attribute.SetPropertyName(valueAttribute.GetPropertyName());
                    });

                    if (tagHelper.IsComponentFullyQualifiedNameMatch())
                    {
                        builder.Metadata[ComponentMetadata.Component.NameMatchKey] = ComponentMetadata.Component.FullyQualifiedNameMatch;
                    }

                    results.Add(builder.Build());
                }
            }

            return(results);
        }
 /// <summary>
 /// Gets a value that indicates whether the property is a parameterized child content property. Properties are
 /// considered parameterized child content if they have the type <c>RenderFragment{T}</c> (for some T).
 /// </summary>
 /// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
 /// <returns>Returns <c>true</c> if the property is parameterized child content, otherwise <c>false</c>.</returns>
 public static bool IsParameterizedChildContentProperty(this BoundAttributeDescriptor attribute)
 {
     return(attribute.IsChildContentProperty() &&
            !string.Equals(attribute.TypeName, ComponentsApi.RenderFragment.FullTypeName, StringComparison.Ordinal));
 }
Ejemplo n.º 7
0
    private static void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer)
    {
        writer.WriteStartObject();

        writer.WritePropertyName(nameof(BoundAttributeDescriptor.Kind));
        writer.WriteValue(boundAttribute.Kind);

        writer.WritePropertyName(nameof(BoundAttributeDescriptor.Name));
        writer.WriteValue(boundAttribute.Name);

        writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName));
        writer.WriteValue(boundAttribute.TypeName);

        if (boundAttribute.IsEnum)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum));
            writer.WriteValue(boundAttribute.IsEnum);
        }

        if (boundAttribute.IndexerNamePrefix != null)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix));
            writer.WriteValue(boundAttribute.IndexerNamePrefix);
        }

        if (boundAttribute.IsEditorRequired)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEditorRequired));
            writer.WriteValue(boundAttribute.IsEditorRequired);
        }

        if (boundAttribute.IndexerTypeName != null)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName));
            writer.WriteValue(boundAttribute.IndexerTypeName);
        }

        if (boundAttribute.Documentation != null)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation));
            writer.WriteValue(boundAttribute.Documentation);
        }

        if (boundAttribute.Diagnostics != null && boundAttribute.Diagnostics.Count > 0)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics));
            serializer.Serialize(writer, boundAttribute.Diagnostics);
        }

        writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata));
        WriteMetadata(writer, boundAttribute.Metadata);

        if (boundAttribute.BoundAttributeParameters != null && boundAttribute.BoundAttributeParameters.Count > 0)
        {
            writer.WritePropertyName(nameof(BoundAttributeDescriptor.BoundAttributeParameters));
            writer.WriteStartArray();
            foreach (var boundAttributeParameter in boundAttribute.BoundAttributeParameters)
            {
                WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer);
            }
            writer.WriteEndArray();
        }

        writer.WriteEndObject();
    }
 public static bool IsCascadingTypeParameterProperty(this BoundAttributeDescriptor attribute)
 {
     return
         (attribute.Metadata.TryGetValue(ComponentMetadata.Component.TypeParameterIsCascadingKey, out var value) &&
          string.Equals(value, bool.TrueString));
 }
 public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute)
 {
     return
         (attribute.Metadata.TryGetValue(ComponentMetadata.Component.GenericTypedKey, out var value) &&
          string.Equals(value, bool.TrueString));
 }
Ejemplo n.º 10
0
            private ComponentChildContentIntermediateNode RewriteChildContent(BoundAttributeDescriptor attribute, SourceSpan?source, IntermediateNodeCollection children)
            {
                var childContent = new ComponentChildContentIntermediateNode()
                {
                    BoundAttribute = attribute,
                    Source         = source,
                    TypeName       = attribute?.TypeName ?? ComponentsApi.RenderFragment.FullTypeName,
                };

                // There are two cases here:
                // 1. Implicit child content - the children will be non-taghelper nodes, just accept them
                // 2. Explicit child content - the children will be various tag helper nodes, that need special processing.
                for (var i = 0; i < children.Count; i++)
                {
                    var child = children[i];
                    if (child is TagHelperBodyIntermediateNode body)
                    {
                        // The body is all of the content we want to render, the rest of the children will
                        // be the attributes.
                        for (var j = 0; j < body.Children.Count; j++)
                        {
                            childContent.Children.Add(body.Children[j]);
                        }
                    }
                    else if (child is TagHelperPropertyIntermediateNode property)
                    {
                        if (property.BoundAttribute.IsChildContentParameterNameProperty())
                        {
                            // Check for each child content with a parameter name, that the parameter name is specified
                            // with literal text. For instance, the following is not allowed and should generate a diagnostic.
                            //
                            // <MyComponent><ChildContent Context="@Foo()">...</ChildContent></MyComponent>
                            if (TryGetAttributeStringContent(property, out var parameterName))
                            {
                                childContent.ParameterName = parameterName;
                                continue;
                            }

                            // The parameter name is invalid.
                            childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidParameter(property.Source, property.AttributeName, attribute.Name));
                            continue;
                        }

                        // This is an unrecognized tag helper bound attribute. This will practically never happen unless the child content descriptor was misconfigured.
                        childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name));
                    }
                    else if (child is TagHelperHtmlAttributeIntermediateNode a)
                    {
                        // This is an HTML attribute on a child content.
                        childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name));
                    }
                    else if (child is TagHelperDirectiveAttributeIntermediateNode directiveAttribute)
                    {
                        // We don't support directive attributes inside child content, this is possible if you try to do something like put '@ref' on a child content.
                        childContent.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidAttribute(directiveAttribute.Source, directiveAttribute.OriginalAttributeName, attribute.Name));
                    }
                    else
                    {
                        // This is some other kind of node (likely an implicit child content)
                        childContent.Children.Add(child);
                    }
                }

                return(childContent);
            }
Ejemplo n.º 11
0
        private void RewriteNodesForEventCallbackBind(
            IntermediateToken original,
            IntermediateToken format,
            BoundAttributeDescriptor valueAttribute,
            BoundAttributeDescriptor changeAttribute,
            List <IntermediateToken> valueExpressionTokens,
            List <IntermediateToken> changeExpressionTokens)
        {
            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetValue(<code>) OR
            // BindMethods.GetValue(<code>, <format>)
            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = $"{ComponentsApi.BindMethods.GetValue}(",
                Kind    = TokenKind.CSharp
            });
            valueExpressionTokens.Add(original);
            if (!string.IsNullOrEmpty(format?.Content))
            {
                valueExpressionTokens.Add(new IntermediateToken()
                {
                    Content = ", ",
                    Kind    = TokenKind.CSharp,
                });
                valueExpressionTokens.Add(format);
            }
            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = ")",
                Kind    = TokenKind.CSharp,
            });

            // Now rewrite the content of the change-handler node. There are two cases we care about
            // here. If it's a component attribute, then don't use the 'CreateBinder' wrapper. We expect
            // component attributes to always 'match' on type.
            //
            // The really tricky part of this is that we CANNOT write the type name of of the EventCallback we
            // intend to create. Doing so would really complicate the story for how we deal with generic types,
            // since the generic type lowering pass runs after this. To keep this simple we're relying on
            // the compiler to resolve overloads for us.
            //
            // EventCallbackFactory.CreateInferred(this, __value => <code> = __value, <code>)
            //
            // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
            // so we use 'CreateBinder'
            //
            // EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>) OR
            // EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>, <format>)
            //
            // Note that the linemappings here are applied to the value attribute, not the change attribute.

            string changeExpressionContent;

            if (changeAttribute == null && format == null)
            {
                // DOM
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                // DOM + format
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content}, {format.Content})";
            }
            else
            {
                // Component
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateInferredMethod}(this, __value => {original.Content} = __value, {original.Content})";
            }
            changeExpressionTokens.Add(new IntermediateToken()
            {
                Content = changeExpressionContent,
                Kind    = TokenKind.CSharp
            });
        }
Ejemplo n.º 12
0
        private void RewriteNodesForDelegateBind(
            IntermediateToken original,
            IntermediateToken format,
            BoundAttributeDescriptor valueAttribute,
            BoundAttributeDescriptor changeAttribute,
            List <IntermediateToken> valueExpressionTokens,
            List <IntermediateToken> changeExpressionTokens)
        {
            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetValue(<code>) OR
            // BindMethods.GetValue(<code>, <format>)
            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = $"{ComponentsApi.BindMethods.GetValue}(",
                Kind    = TokenKind.CSharp
            });
            valueExpressionTokens.Add(original);
            if (!string.IsNullOrEmpty(format?.Content))
            {
                valueExpressionTokens.Add(new IntermediateToken()
                {
                    Content = ", ",
                    Kind    = TokenKind.CSharp,
                });
                valueExpressionTokens.Add(format);
            }
            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = ")",
                Kind    = TokenKind.CSharp,
            });

            // Now rewrite the content of the change-handler node. There are two cases we care about
            // here. If it's a component attribute, then don't use the 'BindMethods' wrapper. We expect
            // component attributes to always 'match' on type.
            //
            // __value => <code> = __value
            //
            // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
            // so we use BindMethods.SetValueHandler
            //
            // BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
            // BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
            //
            // Note that the linemappings here are applied to the value attribute, not the change attribute.

            string changeExpressionContent;

            if (changeAttribute == null && format == null)
            {
                // DOM
                changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                // DOM + format
                changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
            }
            else
            {
                // Component
                changeExpressionContent = $"__value => {original.Content} = __value";
            }
            changeExpressionTokens.Add(new IntermediateToken()
            {
                Content = changeExpressionContent,
                Kind    = TokenKind.CSharp
            });
        }