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); }
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); }
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); }
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); }
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); }
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; }
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)); }
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; }
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)); }
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; }
internal RazorSyntaxTree ParseDocument(RazorLanguageVersion version, string document, bool designTime = false, RazorParserFeatureFlags featureFlags = null) { return(ParseDocument(version, document, null, designTime, featureFlags)); }
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)); }
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())); }
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); }