示例#1
0
        internal void EvaluateData(
            IEnumerable <TagHelperDescriptor> descriptors,
            string documentContent,
            MarkupBlock expectedOutput,
            IEnumerable <RazorDiagnostic> expectedErrors,
            string tagHelperPrefix = null,
            RazorParserFeatureFlags featureFlags = null)
        {
            var syntaxTree        = ParseDocument(documentContent);
            var errorSink         = new ErrorSink();
            var parseTreeRewriter = new TagHelperParseTreeRewriter(
                tagHelperPrefix,
                descriptors,
                featureFlags ?? syntaxTree.Options.FeatureFlags);

            var actualTree = parseTreeRewriter.Rewrite(syntaxTree.Root, errorSink);

            var allErrors    = syntaxTree.Diagnostics.Concat(errorSink.Errors);
            var actualErrors = allErrors
                               .OrderBy(error => error.Span.AbsoluteIndex)
                               .ToList();

            EvaluateRazorErrors(actualErrors, expectedErrors.ToList());
            EvaluateParseTree(actualTree, expectedOutput);
        }
 internal void EvaluateData(
     IEnumerable <TagHelperDescriptor> descriptors,
     string documentContent,
     string tagHelperPrefix = null,
     RazorParserFeatureFlags featureFlags = null)
 {
     EvaluateData(descriptors, documentContent, null, Array.Empty <RazorDiagnostic>(), tagHelperPrefix, featureFlags);
 }
示例#3
0
    public void Create_21Version_Allows21Features()
    {
        // Arrange & Act
        var context = RazorParserFeatureFlags.Create(RazorLanguageVersion.Version_2_1, FileKinds.Legacy);

        // Assert
        Assert.True(context.AllowMinimizedBooleanTagHelperAttributes);
        Assert.True(context.AllowHtmlCommentsInTagHelpers);
    }
示例#4
0
    public void Create_LatestVersion_AllowsLatestFeatures()
    {
        // Arrange & Act
        var context = RazorParserFeatureFlags.Create(RazorLanguageVersion.Latest, FileKinds.Legacy);

        // Assert
        Assert.True(context.AllowComponentFileKind);
        Assert.True(context.AllowRazorInAllCodeBlocks);
        Assert.True(context.AllowUsingVariableDeclarations);
        Assert.True(context.AllowNullableForgivenessOperator);
    }
示例#5
0
        internal void EvaluateData(
            IEnumerable <TagHelperDescriptor> descriptors,
            string documentContent,
            string tagHelperPrefix = null,
            RazorParserFeatureFlags featureFlags = null)
        {
            var syntaxTree = ParseDocument(documentContent, featureFlags: featureFlags);

            var rewrittenTree = TagHelperParseTreeRewriter.Rewrite(syntaxTree, tagHelperPrefix, descriptors);

            BaselineTest(rewrittenTree);
        }
示例#6
0
    public void Create_OldestVersion_DoesNotAllowLatestFeatures()
    {
        // Arrange & Act
        var context = RazorParserFeatureFlags.Create(RazorLanguageVersion.Version_1_0, FileKinds.Legacy);

        // Assert
        Assert.False(context.AllowMinimizedBooleanTagHelperAttributes);
        Assert.False(context.AllowHtmlCommentsInTagHelpers);
        Assert.False(context.AllowComponentFileKind);
        Assert.False(context.AllowRazorInAllCodeBlocks);
        Assert.False(context.AllowUsingVariableDeclarations);
        Assert.False(context.AllowNullableForgivenessOperator);
    }
示例#7
0
            public TestRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives, RazorLanguageVersion version, RazorParserFeatureFlags featureFlags = null)
            {
                if (directives == null)
                {
                    throw new ArgumentNullException(nameof(directives));
                }

                Directives             = directives;
                DesignTime             = designTime;
                ParseLeadingDirectives = parseLeadingDirectives;
                Version      = version;
                FeatureFlags = featureFlags ?? RazorParserFeatureFlags.Create(Version);
            }
 public TagHelperParseTreeRewriter(
     string tagHelperPrefix,
     IEnumerable <TagHelperDescriptor> descriptors,
     RazorParserFeatureFlags featureFlags)
 {
     _tagHelperPrefix       = tagHelperPrefix;
     _tagHelperBinder       = new TagHelperBinder(tagHelperPrefix, descriptors);
     _trackerStack          = new Stack <TagBlockTracker>();
     _blockStack            = new Stack <BlockBuilder>();
     _attributeValueBuilder = new StringBuilder();
     _htmlAttributeTracker  = new List <KeyValuePair <string, string> >();
     _featureFlags          = featureFlags;
 }
示例#9
0
 internal static RazorParserOptions CreateParserOptions(
     RazorLanguageVersion version,
     IEnumerable <DirectiveDescriptor> directives,
     bool designTime,
     RazorParserFeatureFlags featureFlags = null)
 {
     return(new TestRazorParserOptions(
                directives.ToArray(),
                designTime,
                parseLeadingDirectives: false,
                version: version,
                featureFlags: featureFlags));
 }
示例#10
0
        public static MarkupTagHelperStartTagSyntax Rewrite(
            string tagName,
            bool validStructure,
            RazorParserFeatureFlags featureFlags,
            MarkupTagBlockSyntax tag,
            TagHelperBinding bindingResult,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            // There will always be at least one child for the '<'.
            var rewrittenChildren = GetRewrittenChildren(tagName, validStructure, tag, bindingResult, featureFlags, errorSink, source);

            return(SyntaxFactory.MarkupTagHelperStartTag(rewrittenChildren));
        }
    public DefaultRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives, RazorLanguageVersion version, string fileKind)
    {
        if (directives == null)
        {
            throw new ArgumentNullException(nameof(directives));
        }

        Directives             = directives;
        DesignTime             = designTime;
        ParseLeadingDirectives = parseLeadingDirectives;
        Version      = version;
        FeatureFlags = RazorParserFeatureFlags.Create(Version, fileKind);
        FileKind     = fileKind;
    }
示例#12
0
        public static TagHelperBlockBuilder Rewrite(
            string tagName,
            bool validStructure,
            RazorParserFeatureFlags featureFlags,
            Block tag,
            TagHelperBinding bindingResult,
            ErrorSink errorSink)
        {
            // There will always be at least one child for the '<'.
            var start      = tag.Children.First().Start;
            var attributes = GetTagAttributes(tagName, validStructure, tag, bindingResult, errorSink, featureFlags);
            var tagMode    = GetTagMode(tagName, tag, bindingResult, errorSink);

            return(new TagHelperBlockBuilder(tagName, tagMode, start, attributes, bindingResult));
        }
示例#13
0
 public Rewriter(
     RazorSourceDocument source,
     string tagHelperPrefix,
     IEnumerable <TagHelperDescriptor> descriptors,
     RazorParserFeatureFlags featureFlags,
     ErrorSink errorSink)
 {
     _source                = source;
     _tagHelperPrefix       = tagHelperPrefix;
     _tagHelperBinder       = new TagHelperBinder(tagHelperPrefix, descriptors);
     _trackerStack          = new Stack <TagTracker>();
     _attributeValueBuilder = new StringBuilder();
     _htmlAttributeTracker  = new List <KeyValuePair <string, string> >();
     _featureFlags          = featureFlags;
     _errorSink             = errorSink;
 }
示例#14
0
 internal RazorSyntaxTree ParseDocument(RazorLanguageVersion version, string document, bool designTime = false, RazorParserFeatureFlags featureFlags = null)
 {
     return(ParseDocument(version, document, null, designTime, featureFlags));
 }
示例#15
0
        private static SyntaxList <RazorSyntaxNode> GetRewrittenChildren(
            string tagName,
            bool validStructure,
            MarkupTagBlockSyntax tagBlock,
            TagHelperBinding bindingResult,
            RazorParserFeatureFlags featureFlags,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            var tagHelperBuilder = SyntaxListBuilder <RazorSyntaxNode> .Create();

            var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            if (tagBlock.Children.Count == 1)
            {
                // Tag with no attributes. We have nothing to rewrite here.
                return(tagBlock.Children);
            }

            // Add the tag start
            tagHelperBuilder.Add(tagBlock.Children.First());

            // We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" or "/>".
            // If the tag does not have a valid structure then there's no close angle to ignore.
            var tokenOffset = validStructure ? 1 : 0;

            for (var i = 1; i < tagBlock.Children.Count - tokenOffset; i++)
            {
                var            isMinimized           = false;
                var            attributeNameLocation = SourceLocation.Undefined;
                var            child = tagBlock.Children[i];
                TryParseResult result;
                if (child is MarkupAttributeBlockSyntax attributeBlock)
                {
                    attributeNameLocation = attributeBlock.Name.GetSourceLocation(source);
                    result = TryParseAttribute(
                        tagName,
                        attributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    tagHelperBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock)
                {
                    isMinimized           = true;
                    attributeNameLocation = minimizedAttributeBlock.Name.GetSourceLocation(source);
                    result = TryParseMinimizedAttribute(
                        tagName,
                        minimizedAttributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    tagHelperBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is CSharpCodeBlockSyntax)
                {
                    // TODO: Accept more than just Markup attributes: https://github.com/aspnet/Razor/issues/96.
                    // Something like:
                    // <input @checked />
                    var location   = new SourceSpan(child.GetSourceLocation(source), child.FullWidth);
                    var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelpersCannotHaveCSharpInTagDeclaration(location, tagName);
                    errorSink.OnError(diagnostic);

                    result = null;
                }
                else if (child is MarkupTextLiteralSyntax)
                {
                    // If the original span content was whitespace it ultimately means the tag
                    // that owns this "attribute" is malformed and is expecting a user to type a new attribute.
                    // ex: <myTH class="btn"| |
                    var literalContent = child.GetContent();
                    if (!string.IsNullOrWhiteSpace(literalContent))
                    {
                        var location   = child.GetSourceSpan(source);
                        var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperAttributeListMustBeWellFormed(location);
                        errorSink.OnError(diagnostic);
                    }
                    result = null;
                }
                else
                {
                    result = null;
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result == null)
                {
                    // Error occurred while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    for (var j = i; j < tagBlock.Children.Count; j++)
                    {
                        tagHelperBuilder.Add(tagBlock.Children[j]);
                    }

                    return(tagHelperBuilder.ToList());
                }

                // Check if it's a non-boolean bound attribute that is minimized or if it's a bound
                // non-string attribute that has null or whitespace content.
                var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
                if ((isMinimized &&
                     result.IsBoundAttribute &&
                     !isValidMinimizedAttribute) ||
                    (!isMinimized &&
                     result.IsBoundNonStringAttribute &&
                     string.IsNullOrWhiteSpace(GetAttributeValueContent(result.RewrittenAttribute))))
                {
                    var errorLocation    = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors);
                    var diagnostic       = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName);
                    errorSink.OnError(diagnostic);
                }

                // Check if the attribute was a prefix match for a tag helper dictionary property but the
                // dictionary key would be the empty string.
                if (result.IsMissingDictionaryKey)
                {
                    var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var diagnostic    = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName);
                    errorSink.OnError(diagnostic);
                }
            }

            if (validStructure)
            {
                // Add the tag end.
                tagHelperBuilder.Add(tagBlock.Children[tagBlock.Children.Count - 1]);
            }

            return(tagHelperBuilder.ToList());
        }
        internal virtual RazorSyntaxTree ParseDocument(RazorLanguageVersion version, string document, IEnumerable <DirectiveDescriptor> directives, bool designTime = false, RazorParserFeatureFlags featureFlags = null, string fileKind = null)
        {
            directives ??= Array.Empty <DirectiveDescriptor>();

            var source = TestRazorSourceDocument.Create(document, filePath: null, relativePath: null, normalizeNewLines: true);

            var options = CreateParserOptions(version, directives, designTime, featureFlags, fileKind);
            var context = new ParserContext(source, options);

            var codeParser   = new CSharpCodeParser(directives, context);
            var markupParser = new HtmlMarkupParser(context);

            codeParser.HtmlParser   = markupParser;
            markupParser.CodeParser = codeParser;

            var root = markupParser.ParseDocument().CreateRed();

            var diagnostics = context.ErrorSink.Errors;

            var codeDocument = RazorCodeDocument.Create(source);

            var syntaxTree = RazorSyntaxTree.Create(root, source, diagnostics, options);

            codeDocument.SetSyntaxTree(syntaxTree);

            var defaultDirectivePass = new DefaultDirectiveSyntaxTreePass();

            syntaxTree = defaultDirectivePass.Execute(codeDocument, syntaxTree);

            return(syntaxTree);
        }
 internal RazorSyntaxTree ParseDocument(string document, bool designTime = false, IEnumerable <DirectiveDescriptor> directives = null, RazorParserFeatureFlags featureFlags = null, string fileKind = null)
 {
     return(ParseDocument(RazorLanguageVersion.Latest, document, directives, designTime, featureFlags, fileKind));
 }
示例#18
0
        public static MarkupTagHelperStartTagSyntax Rewrite(
            string tagName,
            RazorParserFeatureFlags featureFlags,
            MarkupStartTagSyntax startTag,
            TagHelperBinding bindingResult,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            var attributes       = startTag.Attributes;
            var attributeBuilder = SyntaxListBuilder <RazorSyntaxNode> .Create();

            for (var i = 0; i < startTag.Attributes.Count; i++)
            {
                var            isMinimized           = false;
                var            attributeNameLocation = SourceLocation.Undefined;
                var            child = startTag.Attributes[i];
                TryParseResult result;
                if (child is MarkupAttributeBlockSyntax attributeBlock)
                {
                    attributeNameLocation = attributeBlock.Name.GetSourceLocation(source);
                    result = TryParseAttribute(
                        tagName,
                        attributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    attributeBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock)
                {
                    isMinimized           = true;
                    attributeNameLocation = minimizedAttributeBlock.Name.GetSourceLocation(source);
                    result = TryParseMinimizedAttribute(
                        tagName,
                        minimizedAttributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    attributeBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMiscAttributeContentSyntax miscContent)
                {
                    foreach (var contentChild in miscContent.Children)
                    {
                        if (contentChild is CSharpCodeBlockSyntax codeBlock)
                        {
                            // TODO: Accept more than just Markup attributes: https://github.com/aspnet/Razor/issues/96.
                            // Something like:
                            // <input @checked />
                            var location   = new SourceSpan(codeBlock.GetSourceLocation(source), codeBlock.FullWidth);
                            var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelpersCannotHaveCSharpInTagDeclaration(location, tagName);
                            errorSink.OnError(diagnostic);
                            break;
                        }
                        else
                        {
                            // If the original span content was whitespace it ultimately means the tag
                            // that owns this "attribute" is malformed and is expecting a user to type a new attribute.
                            // ex: <myTH class="btn"| |
                            var literalContent = contentChild.GetContent();
                            if (!string.IsNullOrWhiteSpace(literalContent))
                            {
                                var location   = contentChild.GetSourceSpan(source);
                                var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperAttributeListMustBeWellFormed(location);
                                errorSink.OnError(diagnostic);
                                break;
                            }
                        }
                    }

                    result = null;
                }
                else
                {
                    result = null;
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result == null)
                {
                    // Error occurred while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    for (var j = i; j < startTag.Attributes.Count; j++)
                    {
                        attributeBuilder.Add(startTag.Attributes[j]);
                    }

                    break;
                }

                // Check if it's a non-boolean bound attribute that is minimized or if it's a bound
                // non-string attribute that has null or whitespace content.
                var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
                if ((isMinimized &&
                     result.IsBoundAttribute &&
                     !isValidMinimizedAttribute) ||
                    (!isMinimized &&
                     result.IsBoundNonStringAttribute &&
                     string.IsNullOrWhiteSpace(GetAttributeValueContent(result.RewrittenAttribute))))
                {
                    var errorLocation    = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors);
                    var diagnostic       = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName);
                    errorSink.OnError(diagnostic);
                }

                // Check if the attribute was a prefix match for a tag helper dictionary property but the
                // dictionary key would be the empty string.
                if (result.IsMissingDictionaryKey)
                {
                    var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var diagnostic    = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName);
                    errorSink.OnError(diagnostic);
                }
            }

            if (attributeBuilder.Count > 0)
            {
                // This means we rewrote something. Use the new set of attributes.
                attributes = attributeBuilder.ToList();
            }

            var tagHelperStartTag = SyntaxFactory.MarkupTagHelperStartTag(
                startTag.OpenAngle, startTag.Bang, startTag.Name, attributes, startTag.ForwardSlash, startTag.CloseAngle);

            return(tagHelperStartTag.WithSpanContext(startTag.GetSpanContext()));
        }
示例#19
0
        private static IList <TagHelperAttributeNode> GetTagAttributes(
            string tagName,
            bool validStructure,
            Block tagBlock,
            TagHelperBinding bindingResult,
            ErrorSink errorSink,
            RazorParserFeatureFlags featureFlags)
        {
            var attributes = new List <TagHelperAttributeNode>();

            // We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" or "/>".
            // The -2 accounts for both the start and end tags. If the tag does not have a valid structure then there's
            // no end tag to ignore.
            var symbolOffset                 = validStructure ? 2 : 1;
            var attributeChildren            = tagBlock.Children.Skip(1).Take(tagBlock.Children.Count() - symbolOffset);
            var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (var child in attributeChildren)
            {
                TryParseResult result;
                if (child.IsBlock)
                {
                    result = TryParseBlock(tagName, (Block)child, bindingResult.Descriptors, errorSink, processedBoundAttributeNames);
                }
                else
                {
                    result = TryParseSpan((Span)child, bindingResult.Descriptors, errorSink, processedBoundAttributeNames);
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result != null)
                {
                    // Check if it's a non-boolean bound attribute that is minimized or if it's a bound
                    // non-string attribute that has null or whitespace content.
                    var isMinimized = result.AttributeValueNode == null;
                    var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
                    if ((isMinimized &&
                         result.IsBoundAttribute &&
                         !isValidMinimizedAttribute) ||
                        (!isMinimized &&
                         result.IsBoundNonStringAttribute &&
                         IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
                    {
                        var errorLocation    = GetAttributeNameLocation(child, result.AttributeName);
                        var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors);
                        var diagnostic       = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName);
                        errorSink.OnError(diagnostic);
                    }

                    // Check if the attribute was a prefix match for a tag helper dictionary property but the
                    // dictionary key would be the empty string.
                    if (result.IsMissingDictionaryKey)
                    {
                        var errorLocation = GetAttributeNameLocation(child, result.AttributeName);
                        var diagnostic    = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName);
                        errorSink.OnError(diagnostic);
                    }

                    var attributeNode = new TagHelperAttributeNode(
                        result.AttributeName,
                        result.AttributeValueNode,
                        result.AttributeStructure);

                    attributes.Add(attributeNode);
                }
                else
                {
                    // Error occured while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    break;
                }
            }

            return(attributes);
        }