public void Execute_CombinesErrorsOnRewritingErrors() { // Arrange var engine = RazorEngine.Create(builder => { builder.AddTagHelpers(new[] { CreateTagHelperDescriptor( tagName: "form", typeName: "TestFormTagHelper", assemblyName: "TestAssembly"), CreateTagHelperDescriptor( tagName: "input", typeName: "TestInputTagHelper", assemblyName: "TestAssembly"), }); }); var phase = new DefaultRazorTagHelperBinderPhase() { Engine = engine, }; var content = @" @addTagHelper *, TestAssembly <form> <input value='Hello' type='text' />"; var sourceDocument = TestRazorSourceDocument.Create(content, fileName: null); var codeDocument = RazorCodeDocument.Create(sourceDocument); var originalTree = RazorSyntaxTree.Parse(sourceDocument); var initialError = RazorDiagnostic.Create(new RazorError("Initial test error", SourceLocation.Zero, length: 1)); var expectedRewritingError = RazorDiagnostic.Create( new RazorError( LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper("form"), new SourceLocation(Environment.NewLine.Length * 2 + 30, 2, 1), length: 4)); var erroredOriginalTree = RazorSyntaxTree.Create(originalTree.Root, originalTree.Source, new[] { initialError }, originalTree.Options); codeDocument.SetSyntaxTree(erroredOriginalTree); // Act phase.Execute(codeDocument); // Assert var outputTree = codeDocument.GetSyntaxTree(); Assert.Empty(originalTree.Diagnostics); Assert.NotSame(erroredOriginalTree, outputTree); Assert.Equal(new[] { initialError, expectedRewritingError }, outputTree.Diagnostics); }
private void BuildMalformedTagHelpers(int count, ErrorSink errorSink) { for (var i = 0; i < count; i++) { var tracker = _trackerStack.Peek(); // Skip all non-TagHelper entries. Non TagHelper trackers do not need to represent well-formed HTML. if (!tracker.IsTagHelper) { PopTrackerStack(); continue; } var malformedTagHelper = ((TagHelperBlockTracker)tracker).Builder; errorSink.OnError( SourceLocationTracker.Advance(malformedTagHelper.Start, "<"), LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper( malformedTagHelper.TagName), malformedTagHelper.TagName.Length); BuildCurrentlyTrackedTagHelperBlock(endTag: null); } }
private bool TryRewriteTagHelper(Block tagBlock, ErrorSink errorSink) { // Get tag name of the current block (doesn't matter if it's an end or start tag) var tagName = GetTagName(tagBlock); // Could not determine tag name, it can't be a TagHelper, continue on and track the element. if (tagName == null) { return(false); } TagHelperBinding tagHelperBinding; if (!IsPotentialTagHelper(tagName, tagBlock)) { return(false); } var tracker = _currentTagHelperTracker; var tagNameScope = tracker?.TagName ?? string.Empty; if (!IsEndTag(tagBlock)) { // We're now in a start tag block, we first need to see if the tag block is a tag helper. var elementAttributes = GetAttributeNameValuePairs(tagBlock); tagHelperBinding = _tagHelperBinder.GetBinding( tagName, elementAttributes, CurrentParentTagName, CurrentParentIsTagHelper); // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper if (tagHelperBinding == null) { // If the current tag matches the current TagHelper scope it means the parent TagHelper matched // all the required attributes but the current one did not; therefore, we need to increment the // OpenMatchingTags counter for current the TagHelperBlock so we don't end it too early. // ex: <myth req="..."><myth></myth></myth> We don't want the first myth to close on the inside // tag. if (string.Equals(tagNameScope, tagName, StringComparison.OrdinalIgnoreCase)) { tracker.OpenMatchingTags++; } return(false); } ValidateParentAllowsTagHelper(tagName, tagBlock, errorSink); ValidateBinding(tagHelperBinding, tagName, tagBlock, errorSink); // We're in a start TagHelper block. var validTagStructure = ValidateTagSyntax(tagName, tagBlock, errorSink); var builder = TagHelperBlockRewriter.Rewrite( tagName, validTagStructure, _featureFlags, tagBlock, tagHelperBinding, errorSink); // Track the original start tag so the editor knows where each piece of the TagHelperBlock lies // for formatting. builder.SourceStartTag = tagBlock; // Found a new tag helper block TrackTagHelperBlock(builder); // If it's a non-content expecting block then we don't have to worry about nested children within the // tag. Complete it. if (builder.TagMode == TagMode.SelfClosing || builder.TagMode == TagMode.StartTagOnly) { BuildCurrentlyTrackedTagHelperBlock(endTag: null); } } else { // Validate that our end tag matches the currently scoped tag, if not we may need to error. if (tagNameScope.Equals(tagName, StringComparison.OrdinalIgnoreCase)) { // If there are additional end tags required before we can build our block it means we're in a // situation like this: <myth req="..."><myth></myth></myth> where we're at the inside </myth>. if (tracker.OpenMatchingTags > 0) { tracker.OpenMatchingTags--; return(false); } ValidateTagSyntax(tagName, tagBlock, errorSink); BuildCurrentlyTrackedTagHelperBlock(tagBlock); } else { tagHelperBinding = _tagHelperBinder.GetBinding( tagName, attributes: Array.Empty <KeyValuePair <string, string> >(), parentTagName: CurrentParentTagName, parentIsTagHelper: CurrentParentIsTagHelper); // If there are not TagHelperDescriptors associated with the end tag block that also have no // required attributes then it means we can't be a TagHelper, bail out. if (tagHelperBinding == null) { return(false); } foreach (var descriptor in tagHelperBinding.Descriptors) { var boundRules = tagHelperBinding.GetBoundRules(descriptor); var invalidRule = boundRules.FirstOrDefault(rule => rule.TagStructure == TagStructure.WithoutEndTag); if (invalidRule != null) { // End tag TagHelper that states it shouldn't have an end tag. errorSink.OnError( SourceLocationTracker.Advance(tagBlock.Start, "</"), LegacyResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag( tagName, descriptor.DisplayName, invalidRule.TagStructure), tagName.Length); return(false); } } // Current tag helper scope does not match the end tag. Attempt to recover the tag // helper by looking up the previous tag helper scopes for a matching tag. If we // can't recover it means there was no corresponding tag helper start tag. if (TryRecoverTagHelper(tagName, tagBlock, errorSink)) { ValidateParentAllowsTagHelper(tagName, tagBlock, errorSink); ValidateTagSyntax(tagName, tagBlock, errorSink); // Successfully recovered, move onto the next element. } else { // Could not recover, the end tag helper has no corresponding start tag, create // an error based on the current childBlock. errorSink.OnError( SourceLocationTracker.Advance(tagBlock.Start, "</"), LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName), tagName.Length); return(false); } } } return(true); }