示例#1
0
        public void AddSpanAddsSpanToCurrentBlockBuilder()
        {
            // Arrange
            var factory = SpanFactory.CreateCsHtml();
            var mockListener = new Mock<ParserVisitor>();
            var context = SetupTestContext("phoo");

            var builder = new SpanBuilder()
            {
                Kind = SpanKind.Code
            };
            builder.Accept(new CSharpSymbol(1, 0, 1, "foo", CSharpSymbolType.Identifier));
            var added = builder.Build();

            using (context.StartBlock(BlockType.Functions))
            {
                context.AddSpan(added);
            }

            var expected = new BlockBuilder()
            {
                Type = BlockType.Functions,
            };
            expected.Children.Add(added);

            // Assert
            ParserTestBase.EvaluateResults(context.CompleteParse(), expected.Build());
        }
示例#2
0
        public void AddSpanAddsSpanToCurrentBlockBuilder()
        {
            // Arrange
            var factory = SpanFactory.CreateCsHtml();
            Mock <ParserVisitor> mockListener = new Mock <ParserVisitor>();
            var context = SetupTestContext("phoo");

            var builder = new SpanBuilder()
            {
                Kind = SpanKind.Code
            };

            builder.Accept(new CSharpSymbol(1, 0, 1, "foo", CSharpSymbolType.Identifier));
            var added = builder.Build();

            using (context.StartBlock(BlockType.Functions))
            {
                context.AddSpan(added);
            }

            var expected = new BlockBuilder()
            {
                Type = BlockType.Functions,
            };

            expected.Children.Add(added);

            // Assert
            ParserTestBase.EvaluateResults(context.CompleteParse(), expected.Build());
        }
        //public override void VisitBlock(Block block)
        //{
        //    BlockBuilder parent = null;
        //    if (_blocks.Count > 0)
        //    {
        //        parent = _blocks.Peek();
        //    }
        //    BlockBuilder newBlock = new BlockBuilder(block);
        //    newBlock.Children.Clear();
        //    _blocks.Push(newBlock);
        //    if (block.Type == BlockType.Expression && parent != null)
        //    {
        //        VisitExpressionBlock(block, parent);
        //    }
        //    else
        //    {
        //        base.VisitBlock(block);
        //    }
        //    if (_blocks.Count > 1)
        //    {
        //        parent.Children.Add(_blocks.Pop().Build());
        //    }
        //}

        //public override void VisitSpan(Span span)
        //{
        //    Debug.Assert(_blocks.Count > 0);
        //    _blocks.Peek().Children.Add(span);
        //}

        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            BlockBuilder newBlock = new BlockBuilder(block);

            newBlock.Children.Clear();
            Span ws = block.Children.FirstOrDefault() as Span;
            IEnumerable <SyntaxTreeNode> newNodes = block.Children;

            if (ws.Content.All(Char.IsWhiteSpace))
            {
                // Add this node to the parent
                SpanBuilder builder = new SpanBuilder(ws);
                builder.ClearSymbols();
                FillSpan(builder, ws.Start, ws.Content);
                parent.Children.Add(builder.Build());

                // Remove the old whitespace node
                newNodes = block.Children.Skip(1);
            }

            foreach (SyntaxTreeNode node in newNodes)
            {
                newBlock.Children.Add(node);
            }
            return(newBlock.Build());
        }
示例#4
0
        public void AddLiteralChunk(string literal, SyntaxTreeNode association)
        {
            // If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.
            var literalChunk = _lastChunk as LiteralChunk;

            if (literalChunk != null)
            {
                // Literal chunks are always associated with Spans
                var lastSpan    = (Span)literalChunk.Association;
                var currentSpan = (Span)association;

                var builder = new SpanBuilder(lastSpan);
                foreach (var symbol in currentSpan.Symbols)
                {
                    builder.Accept(symbol);
                }

                literalChunk.Association = builder.Build();
                literalChunk.Text       += literal;
            }
            else
            {
                AddChunk(new LiteralChunk
                {
                    Text = literal,
                }, association);
            }
        }
示例#5
0
        public static Span BuildSpan(string content, SpanKind kind)
        {
            SpanBuilder builder = new SpanBuilder();

            builder.Kind = kind;
            builder.Accept(new HtmlSymbol(new SourceLocation(), content, HtmlSymbolType.Text));

            return(builder.Build());
        }
示例#6
0
        protected override SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span)
        {
            var b   = new SpanBuilder(span);
            var old = (LiteralAttributeCodeGenerator)span.CodeGenerator;

            b.CodeGenerator = old.ValueGenerator != null
                                ? new PreprocessedLiteralAttributeCodeGenerator(old.Prefix, old.ValueGenerator)
                                : new PreprocessedLiteralAttributeCodeGenerator(old.Prefix, old.Value);
            return(b.Build());
        }
示例#7
0
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static KeyValuePair <string, SyntaxTreeNode> ParseSpan(Span span)
        {
            var afterEquals = false;
            var builder     = new SpanBuilder
            {
                CodeGenerator = span.CodeGenerator,
                EditHandler   = span.EditHandler,
                Kind          = span.Kind
            };
            var    htmlSymbols  = span.Symbols.OfType <HtmlSymbol>().ToArray();
            var    symbolOffset = 1;
            string name         = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (name == null && symbol.Type == HtmlSymbolType.Text)
                {
                    name = symbol.Content;
                }
                else if (afterEquals)
                {
                    builder.Accept(symbol);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    // We've found an '=' symbol, this means that the coming symbols will either be a quote
                    // or value (in the case that the value is unquoted).
                    // Spaces after/before the equal symbol are not yet supported:
                    // https://github.com/aspnet/Razor/issues/123

                    // TODO: Handle malformed tags, if there's an '=' then there MUST be a value.
                    // https://github.com/aspnet/Razor/issues/104

                    // Check for attribute start values, aka single or double quote
                    if (IsQuote(htmlSymbols[i + 1]))
                    {
                        // Move past the attribute start so we can accept the true value.
                        i++;
                    }
                    else
                    {
                        // Set the symbol offset to 0 so we don't attempt to skip an end quote that doesn't exist.
                        symbolOffset = 0;
                    }

                    afterEquals = true;
                }
            }

            return(new KeyValuePair <string, SyntaxTreeNode>(name, builder.Build()));
        }
        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            // Collect the content of this node
            string content = String.Concat(block.Children.Cast <Span>().Select(s => s.Content));

            // Create a new span containing this content
            SpanBuilder span = new SpanBuilder();

            FillSpan(span, block.Children.Cast <Span>().First().Start, content);
            return(span.Build());
        }
        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            // Collect the content of this node
            var content = string.Concat(block.Children.Cast<Span>().Select(s => s.Content));

            // Create a new span containing this content
            var span = new SpanBuilder();
            span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize);
            FillSpan(span, block.Children.Cast<Span>().First().Start, content);
            return span.Build();
        }
示例#10
0
        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            // Collect the content of this node
            var content = string.Concat(block.Children.Cast <Span>().Select(s => s.Content));

            // Create a new span containing this content
            var span = new SpanBuilder();

            span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize);
            FillSpan(span, block.Children.Cast <Span>().First().Start, content);
            return(span.Build());
        }
        public void IsCSharpOpenCurlyBrace_SpanWithHtmlSymbol_ReturnFalse()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(SyntaxKind.Text, "hello"));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
        public void IsCSharpOpenCurlyBrace_SpanWithLeftBrace_ReturnTrue()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(SyntaxKind.LeftBrace, "{"));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.True(result);
        }
示例#13
0
        private static Span GetSpan(SourceLocation start, string content)
        {
            var spanBuilder = new SpanBuilder(start);
            var tokens      = CSharpLanguageCharacteristics.Instance.TokenizeString(content).ToArray();

            foreach (var token in tokens)
            {
                spanBuilder.Accept(token);
            }
            var span = spanBuilder.Build();

            return(span);
        }
        private static Span CreateMarkupAttribute(SpanBuilder builder, bool isBoundNonStringAttribute)
        {
            Debug.Assert(builder != null);

            // If the attribute was requested by a tag helper but the corresponding property was not a string,
            // then treat its value as code. A non-string value can be any C# value so we need to ensure the
            // SyntaxTreeNode reflects that.
            if (isBoundNonStringAttribute)
            {
                builder.Kind = SpanKind.Code;
            }

            return(builder.Build());
        }
        public void IsCSharpOpenCurlyBrace_SpanWithUnsupportedSymbolType_ReturnFalse(string content, object symbolTypeObject)
        {
            // Arrange
            var symbolType = (SyntaxKind)symbolTypeObject;
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(symbolType, content));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
示例#16
0
        public void IsCSharpOpenCurlyBrace_SpanWithLeftBrace_ReturnTrue()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);

            childBuilder.Accept(new CSharpSymbol("{", CSharpSymbolType.LeftBrace));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.True(result);
        }
示例#17
0
        protected override SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span)
        {
            // Only rewrite if we have a previous that is also markup (CanRewrite does this check for us!)
            var previous = parent.Children.LastOrDefault() as Span;
            if (previous == null || !CanRewrite(previous))
            {
                return span;
            }

            // Merge spans
            parent.Children.Remove(previous);
            var merged = new SpanBuilder();
            FillSpan(merged, previous.Start, previous.Content + span.Content);
            return merged.Build();
        }
        public void IsCSharpOpenCurlyBrace_MultipleSymbols_ReturnFalse()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);

            childBuilder.Accept(new CSharpToken("hello", CSharpTokenType.Identifier));
            childBuilder.Accept(new CSharpToken(",", CSharpTokenType.Comma));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
示例#19
0
        protected override SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span)
        {
            // Only rewrite if we have a previous that is also markup (CanRewrite does this check for us!)
            Span previous = parent.Children.LastOrDefault() as Span;

            if (previous == null || !CanRewrite(previous))
            {
                return(span);
            }

            // Merge spans
            parent.Children.Remove(previous);
            SpanBuilder merged = new SpanBuilder();

            FillSpan(merged, previous.Start, previous.Content + span.Content);
            return(merged.Build());
        }
示例#20
0
        private static Span CreateCodeSpan(string code)
        {
            // TODO: Correctly reconstruct the syntax tree with correct symbols
            var builder = new SpanBuilder();

            builder.Kind = SpanKind.Code;

            builder.Accept(
                new HtmlSymbol(
                    new SourceLocation(0, 0, 0),
                    code,
                    HtmlSymbolType.Text
                    )
                );

            return(builder.Build());
        }
示例#21
0
        private static KeyValuePair <string, SyntaxTreeNode> CreateMarkupAttribute(
            string name,
            SpanBuilder builder,
            IReadOnlyDictionary <string, string> attributeValueTypes)
        {
            string attributeTypeName;

            // If the attribute was requested by the tag helper and doesn't happen to be a string then we need to treat
            // its value as code. Any non-string value can be any C# value so we need to ensure the SyntaxTreeNode
            // reflects that.
            if (attributeValueTypes.TryGetValue(name, out attributeTypeName) &&
                !IsStringAttribute(attributeTypeName))
            {
                builder.Kind = SpanKind.Code;
            }

            return(new KeyValuePair <string, SyntaxTreeNode>(name, builder.Build()));
        }
示例#22
0
        private static SyntaxTreeNode CreateMarkupAttribute(SpanBuilder builder, bool isBoundNonStringAttribute)
        {
            Span value = null;

            // Builder will be null in the case of minimized attributes
            if (builder != null)
            {
                // If the attribute was requested by a tag helper but the corresponding property was not a string,
                // then treat its value as code. A non-string value can be any C# value so we need to ensure the
                // SyntaxTreeNode reflects that.
                if (isBoundNonStringAttribute)
                {
                    builder.Kind = SpanKind.Code;
                }

                value = builder.Build();
            }

            return(value);
        }
        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            // Collect the content of this node
            var builder = new StringBuilder();

            for (var i = 0; i < block.Children.Count; i++)
            {
                var childSpan = (Span)block.Children[i];
                builder.Append(childSpan.Content);
            }

            // Create a new span containing this content
            var span = new SpanBuilder();

            span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize);
            Debug.Assert(block.Children.Count > 0);
            var start = ((Span)block.Children[0]).Start;

            FillSpan(span, start, builder.ToString());
            return(span.Build());
        }
示例#24
0
        public Span Span(SpanKind kind, params ISymbol[] symbols)
        {
            var builder = new SpanBuilder();

            builder.Kind = kind;

            foreach (var symbol in symbols)
            {
                builder.Accept(symbol);
            }

            var span = builder.Build();

            if (_last != null)
            {
                span.Previous = _last;
                _last.Next    = span;
            }
            _last = span;

            return(span);
        }
示例#25
0
        protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
        {
            var newBlock = new BlockBuilder(block);
            newBlock.Children.Clear();
            var ws = block.Children.FirstOrDefault() as Span;
            IEnumerable<SyntaxTreeNode> newNodes = block.Children;
            if (ws.Content.All(Char.IsWhiteSpace))
            {
                // Add this node to the parent
                var builder = new SpanBuilder(ws);
                builder.ClearSymbols();
                FillSpan(builder, ws.Start, ws.Content);
                parent.Children.Add(builder.Build());

                // Remove the old whitespace node
                newNodes = block.Children.Skip(1);
            }

            foreach (SyntaxTreeNode node in newNodes)
            {
                newBlock.Children.Add(node);
            }
            return newBlock.Build();
        }
示例#26
0
        private static TryParseResult TryParseBlock(
            string tagName,
            Block block,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
            // The first child will only ever NOT be a Span if a user is doing something like:
            // <input @checked />

            var childSpan = block.Children.First() as Span;

            if (childSpan == null || childSpan.Kind != SpanKind.Markup)
            {
                errorSink.OnError(
                    block.Start,
                    RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName),
                    block.Length);

                return null;
            }

            var builder = new BlockBuilder(block);

            // If there's only 1 child it means that it's plain text inside of the attribute.
            // i.e. <div class="plain text in attribute">
            if (builder.Children.Count == 1)
            {
                return TryParseSpan(childSpan, descriptors, errorSink);
            }

            var nameSymbols = childSpan
                .Symbols
                .OfType<HtmlSymbol>()
                .SkipWhile(symbol => !HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) // Skip prefix
                .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                .Select(nameSymbol => nameSymbol.Content);

            var name = string.Concat(nameSymbols);
            if (string.IsNullOrEmpty(name))
            {
                errorSink.OnError(
                    childSpan.Start,
                    RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName),
                    childSpan.Length);

                return null;
            }

            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(name, descriptors);

            var firstChild = builder.Children[0] as Span;
            if (firstChild != null && firstChild.Symbols[0] is HtmlSymbol)
            {
                var htmlSymbol = firstChild.Symbols[firstChild.Symbols.Count - 1] as HtmlSymbol;
                switch (htmlSymbol.Type)
                {
                    // Treat NoQuotes and DoubleQuotes equivalently. We purposefully do not persist NoQuotes
                    // ValueStyles at code generation time to protect users from rendering dynamic content with spaces
                    // that can break attributes.
                    // Ex: <tag my-attribute=@value /> where @value results in the test "hello world".
                    // This way, the above code would render <tag my-attribute="hello world" />.
                    case HtmlSymbolType.Equals:
                    case HtmlSymbolType.DoubleQuote:
                        result.AttributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes;
                        break;
                    case HtmlSymbolType.SingleQuote:
                        result.AttributeValueStyle = HtmlAttributeValueStyle.SingleQuotes;
                        break;
                    default:
                        result.AttributeValueStyle = HtmlAttributeValueStyle.Minimized;
                        break;
                }
            }

            // Remove first child i.e. foo="
            builder.Children.RemoveAt(0);

            // Grabbing last child to check if the attribute value is quoted.
            var endNode = block.Children.Last();
            if (!endNode.IsBlock)
            {
                var endSpan = (Span)endNode;

                // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more
                // than a single HTML symbol. Do not ignore those other symbols.
                var symbolCount = endSpan.Symbols.Count();
                var endSymbol = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null;

                // Checking to see if it's a quoted attribute, if so we should remove end quote
                if (endSymbol != null && IsQuote(endSymbol))
                {
                    builder.Children.RemoveAt(builder.Children.Count - 1);
                }
            }

            // We need to rebuild the chunk generators of the builder and its children (this is needed to
            // ensure we don't do special attribute chunk generation since this is a tag helper).
            block = RebuildChunkGenerators(builder.Build(), result.IsBoundAttribute);

            // If there's only 1 child at this point its value could be a simple markup span (treated differently than
            // block level elements for attributes).
            if (block.Children.Count() == 1)
            {
                var child = block.Children.First() as Span;
                if (child != null)
                {
                    // After pulling apart the block we just have a value span.
                    var spanBuilder = new SpanBuilder(child);

                    result.AttributeValueNode =
                        CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute);

                    return result;
                }
            }

            var isFirstSpan = true;

            result.AttributeValueNode = ConvertToMarkupAttributeBlock(
                block,
                (parentBlock, span) =>
                {
                    // If the attribute was requested by a tag helper but the corresponding property was not a
                    // string, then treat its value as code. A non-string value can be any C# value so we need
                    // to ensure the SyntaxTreeNode reflects that.
                    if (result.IsBoundNonStringAttribute)
                    {
                        // For bound non-string attributes, we'll only allow a transition span to appear at the very
                        // beginning of the attribute expression. All later transitions would appear as code so that
                        // they are part of the generated output. E.g.
                        // key="@value" -> MyTagHelper.key = value
                        // key=" @value" -> MyTagHelper.key =  @value
                        // key="1 + @case" -> MyTagHelper.key = 1 + @case
                        // key="@int + @case" -> MyTagHelper.key = int + @case
                        // key="@(a + b) -> MyTagHelper.key = a + b
                        // key="4 + @(a + b)" -> MyTagHelper.key = 4 + @(a + b)
                        if (isFirstSpan && span.Kind == SpanKind.Transition)
                        {
                            // do nothing.
                        }
                        else
                        {
                            var spanBuilder = new SpanBuilder(span);

                            if (parentBlock.Type == BlockType.Expression &&
                                (spanBuilder.Kind == SpanKind.Transition ||
                                spanBuilder.Kind == SpanKind.MetaCode))
                            {
                                // Change to a MarkupChunkGenerator so that the '@' \ parenthesis is generated as part of the output.
                                spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
                            }

                            spanBuilder.Kind = SpanKind.Code;
                            span = spanBuilder.Build();
                        }
                    }

                    isFirstSpan = false;

                    return span;
                });

            return result;
        }
示例#27
0
        private static Block RebuildCodeGenerators(Block block)
        {
            var builder = new BlockBuilder(block);

            var isDynamic = builder.CodeGenerator is DynamicAttributeBlockCodeGenerator;

            // We don't want any attribute specific logic here, null out the block code generator.
            if (isDynamic || builder.CodeGenerator is AttributeBlockCodeGenerator)
            {
                builder.CodeGenerator = BlockCodeGenerator.Null;
            }

            for (var i = 0; i < builder.Children.Count; i++)
            {
                var child = builder.Children[i];

                if (child.IsBlock)
                {
                    // The child is a block, recurse down into the block to rebuild its children
                    builder.Children[i] = RebuildCodeGenerators((Block)child);
                }
                else
                {
                    var childSpan = (Span)child;
                    ISpanCodeGenerator newCodeGenerator = null;
                    var literalGenerator = childSpan.CodeGenerator as LiteralAttributeCodeGenerator;

                    if (literalGenerator != null)
                    {
                        if (literalGenerator.ValueGenerator == null || literalGenerator.ValueGenerator.Value == null)
                        {
                            newCodeGenerator = new MarkupCodeGenerator();
                        }
                        else
                        {
                            newCodeGenerator = literalGenerator.ValueGenerator.Value;
                        }
                    }
                    else if (isDynamic && childSpan.CodeGenerator == SpanCodeGenerator.Null)
                    {
                        // Usually the dynamic code generator handles rendering the null code generators underneath
                        // it. This doesn't make sense in terms of tag helpers though, we need to change null code
                        // generators to markup code generators.

                        newCodeGenerator = new MarkupCodeGenerator();
                    }

                    // If we have a new code generator we'll need to re-build the child
                    if (newCodeGenerator != null)
                    {
                        var childSpanBuilder = new SpanBuilder(childSpan)
                        {
                            CodeGenerator = newCodeGenerator
                        };

                        builder.Children[i] = childSpanBuilder.Build();
                    }
                }
            }

            return(builder.Build());
        }
        private static Block RebuildChunkGenerators(Block block, bool isBound)
        {
            var builder = new BlockBuilder(block);

            // Don't want to rebuild unbound dynamic attributes. They need to run through the conditional attribute
            // removal system at runtime. A conditional attribute at the parse tree rewriting level is defined by
            // having at least 1 child with a DynamicAttributeBlockChunkGenerator.
            if (!isBound &&
                block.Children.Any(
                    child => child.IsBlock &&
                    ((Block)child).ChunkGenerator is DynamicAttributeBlockChunkGenerator))
            {
                // The parent chunk generator must be removed because it's normally responsible for conditionally
                // generating the attribute prefix (class=") and suffix ("). The prefix and suffix concepts aren't
                // applicable for the TagHelper use case since the attributes are put into a dictionary like object as
                // name value pairs.
                builder.ChunkGenerator = ParentChunkGenerator.Null;

                return(builder.Build());
            }

            var isDynamic = builder.ChunkGenerator is DynamicAttributeBlockChunkGenerator;

            // We don't want any attribute specific logic here, null out the block chunk generator.
            if (isDynamic || builder.ChunkGenerator is AttributeBlockChunkGenerator)
            {
                builder.ChunkGenerator = ParentChunkGenerator.Null;
            }

            for (var i = 0; i < builder.Children.Count; i++)
            {
                var child = builder.Children[i];

                if (child.IsBlock)
                {
                    // The child is a block, recurse down into the block to rebuild its children
                    builder.Children[i] = RebuildChunkGenerators((Block)child, isBound);
                }
                else
                {
                    var childSpan = (Span)child;
                    ISpanChunkGenerator newChunkGenerator = null;
                    var literalGenerator = childSpan.ChunkGenerator as LiteralAttributeChunkGenerator;

                    if (literalGenerator != null)
                    {
                        if (literalGenerator.ValueGenerator == null || literalGenerator.ValueGenerator.Value == null)
                        {
                            newChunkGenerator = new MarkupChunkGenerator();
                        }
                        else
                        {
                            newChunkGenerator = literalGenerator.ValueGenerator.Value;
                        }
                    }
                    else if (isDynamic && childSpan.ChunkGenerator == SpanChunkGenerator.Null)
                    {
                        // Usually the dynamic chunk generator handles creating the null chunk generators underneath
                        // it. This doesn't make sense in terms of tag helpers though, we need to change null code
                        // generators to markup chunk generators.

                        newChunkGenerator = new MarkupChunkGenerator();
                    }

                    // If we have a new chunk generator we'll need to re-build the child
                    if (newChunkGenerator != null)
                    {
                        var childSpanBuilder = new SpanBuilder(childSpan)
                        {
                            ChunkGenerator = newChunkGenerator
                        };

                        builder.Children[i] = childSpanBuilder.Build();
                    }
                }
            }

            return(builder.Build());
        }
        private static TryParseResult TryParseBlock(
            string tagName,
            Block block,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
            // The first child will only ever NOT be a Span if a user is doing something like:
            // <input @checked />

            var childSpan = block.Children.First() as Span;

            if (childSpan == null || childSpan.Kind != SpanKind.Markup)
            {
                errorSink.OnError(
                    block.Start,
                    RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName),
                    block.Length);

                return(null);
            }

            var builder = new BlockBuilder(block);

            // If there's only 1 child it means that it's plain text inside of the attribute.
            // i.e. <div class="plain text in attribute">
            if (builder.Children.Count == 1)
            {
                return(TryParseSpan(childSpan, descriptors, errorSink));
            }

            var nameSymbols = childSpan
                              .Symbols
                              .OfType <HtmlSymbol>()
                              .SkipWhile(symbol => !HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) // Skip prefix
                              .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                              .Select(nameSymbol => nameSymbol.Content);

            var name = string.Concat(nameSymbols);

            if (string.IsNullOrEmpty(name))
            {
                errorSink.OnError(
                    childSpan.Start,
                    RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName),
                    childSpan.Length);

                return(null);
            }

            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(name, descriptors);

            var firstChild = builder.Children[0] as Span;

            if (firstChild != null && firstChild.Symbols[0] is HtmlSymbol)
            {
                var htmlSymbol = firstChild.Symbols[firstChild.Symbols.Count - 1] as HtmlSymbol;
                switch (htmlSymbol.Type)
                {
                // Treat NoQuotes and DoubleQuotes equivalently. We purposefully do not persist NoQuotes
                // ValueStyles at code generation time to protect users from rendering dynamic content with spaces
                // that can break attributes.
                // Ex: <tag my-attribute=@value /> where @value results in the test "hello world".
                // This way, the above code would render <tag my-attribute="hello world" />.
                case HtmlSymbolType.Equals:
                case HtmlSymbolType.DoubleQuote:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes;
                    break;

                case HtmlSymbolType.SingleQuote:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.SingleQuotes;
                    break;

                default:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.Minimized;
                    break;
                }
            }

            // Remove first child i.e. foo="
            builder.Children.RemoveAt(0);

            // Grabbing last child to check if the attribute value is quoted.
            var endNode = block.Children.Last();

            if (!endNode.IsBlock)
            {
                var endSpan = (Span)endNode;

                // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more
                // than a single HTML symbol. Do not ignore those other symbols.
                var symbolCount = endSpan.Symbols.Count();
                var endSymbol   = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null;

                // Checking to see if it's a quoted attribute, if so we should remove end quote
                if (endSymbol != null && IsQuote(endSymbol))
                {
                    builder.Children.RemoveAt(builder.Children.Count - 1);
                }
            }

            // We need to rebuild the chunk generators of the builder and its children (this is needed to
            // ensure we don't do special attribute chunk generation since this is a tag helper).
            block = RebuildChunkGenerators(builder.Build(), result.IsBoundAttribute);

            // If there's only 1 child at this point its value could be a simple markup span (treated differently than
            // block level elements for attributes).
            if (block.Children.Count() == 1)
            {
                var child = block.Children.First() as Span;
                if (child != null)
                {
                    // After pulling apart the block we just have a value span.
                    var spanBuilder = new SpanBuilder(child);

                    result.AttributeValueNode =
                        CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute);

                    return(result);
                }
            }

            var isFirstSpan = true;

            result.AttributeValueNode = ConvertToMarkupAttributeBlock(
                block,
                (parentBlock, span) =>
            {
                // If the attribute was requested by a tag helper but the corresponding property was not a
                // string, then treat its value as code. A non-string value can be any C# value so we need
                // to ensure the SyntaxTreeNode reflects that.
                if (result.IsBoundNonStringAttribute)
                {
                    // For bound non-string attributes, we'll only allow a transition span to appear at the very
                    // beginning of the attribute expression. All later transitions would appear as code so that
                    // they are part of the generated output. E.g.
                    // key="@value" -> MyTagHelper.key = value
                    // key=" @value" -> MyTagHelper.key =  @value
                    // key="1 + @case" -> MyTagHelper.key = 1 + @case
                    // key="@int + @case" -> MyTagHelper.key = int + @case
                    // key="@(a + b) -> MyTagHelper.key = a + b
                    // key="4 + @(a + b)" -> MyTagHelper.key = 4 + @(a + b)
                    if (isFirstSpan && span.Kind == SpanKind.Transition)
                    {
                        // do nothing.
                    }
                    else
                    {
                        var spanBuilder = new SpanBuilder(span);

                        if (parentBlock.Type == BlockType.Expression &&
                            (spanBuilder.Kind == SpanKind.Transition ||
                             spanBuilder.Kind == SpanKind.MetaCode))
                        {
                            // Change to a MarkupChunkGenerator so that the '@' \ parenthesis is generated as part of the output.
                            spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
                        }

                        spanBuilder.Kind = SpanKind.Code;
                        span             = spanBuilder.Build();
                    }
                }

                isFirstSpan = false;

                return(span);
            });

            return(result);
        }
示例#30
0
        private static Span CreateMarkupAttribute(SpanBuilder builder, bool isBoundNonStringAttribute)
        {
            Debug.Assert(builder != null);

            // If the attribute was requested by a tag helper but the corresponding property was not a string,
            // then treat its value as code. A non-string value can be any C# value so we need to ensure the
            // SyntaxTreeNode reflects that.
            if (isBoundNonStringAttribute)
            {
                builder.Kind = SpanKind.Code;
            }

            return builder.Build();
        }
示例#31
0
        private static Block RebuildChunkGenerators(Block block, bool isBound)
        {
            var builder = new BlockBuilder(block);

            // Don't want to rebuild unbound dynamic attributes. They need to run through the conditional attribute
            // removal system at runtime. A conditional attribute at the parse tree rewriting level is defined by
            // having at least 1 child with a DynamicAttributeBlockChunkGenerator.
            if (!isBound &&
                block.Children.Any(
                    child => child.IsBlock &&
                    ((Block)child).ChunkGenerator is DynamicAttributeBlockChunkGenerator))
            {
                // The parent chunk generator must be removed because it's normally responsible for conditionally
                // generating the attribute prefix (class=") and suffix ("). The prefix and suffix concepts aren't
                // applicable for the TagHelper use case since the attributes are put into a dictionary like object as
                // name value pairs.
                builder.ChunkGenerator = ParentChunkGenerator.Null;

                return builder.Build();
            }

            var isDynamic = builder.ChunkGenerator is DynamicAttributeBlockChunkGenerator;

            // We don't want any attribute specific logic here, null out the block chunk generator.
            if (isDynamic || builder.ChunkGenerator is AttributeBlockChunkGenerator)
            {
                builder.ChunkGenerator = ParentChunkGenerator.Null;
            }

            for (var i = 0; i < builder.Children.Count; i++)
            {
                var child = builder.Children[i];

                if (child.IsBlock)
                {
                    // The child is a block, recurse down into the block to rebuild its children
                    builder.Children[i] = RebuildChunkGenerators((Block)child, isBound);
                }
                else
                {
                    var childSpan = (Span)child;
                    ISpanChunkGenerator newChunkGenerator = null;
                    var literalGenerator = childSpan.ChunkGenerator as LiteralAttributeChunkGenerator;

                    if (literalGenerator != null)
                    {
                        if (literalGenerator.ValueGenerator == null || literalGenerator.ValueGenerator.Value == null)
                        {
                            newChunkGenerator = new MarkupChunkGenerator();
                        }
                        else
                        {
                            newChunkGenerator = literalGenerator.ValueGenerator.Value;
                        }
                    }
                    else if (isDynamic && childSpan.ChunkGenerator == SpanChunkGenerator.Null)
                    {
                        // Usually the dynamic chunk generator handles creating the null chunk generators underneath
                        // it. This doesn't make sense in terms of tag helpers though, we need to change null code
                        // generators to markup chunk generators.

                        newChunkGenerator = new MarkupChunkGenerator();
                    }

                    // If we have a new chunk generator we'll need to re-build the child
                    if (newChunkGenerator != null)
                    {
                        var childSpanBuilder = new SpanBuilder(childSpan)
                        {
                            ChunkGenerator = newChunkGenerator
                        };

                        builder.Children[i] = childSpanBuilder.Build();
                    }
                }
            }

            return builder.Build();
        }