// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using Moq; using Xunit; namespace Microsoft.AspNetCore.Razor.Language.Legacy { public class HtmlAttributeTest : CsHtmlMarkupParserTestBase { public static TheoryData SymbolBoundAttributeNames { get { return new TheoryData { "[item]", "[(item,", "(click)", "(^click)", "*something", "#local", }; } } [Theory] [MemberData(nameof(SymbolBoundAttributeNames))] public void SymbolBoundAttributes_BeforeEqualWhitespace(string attributeName) { // Arrange var attributeNameLength = attributeName.Length; var newlineLength = Environment.NewLine.Length; var prefixLocation1 = new SourceLocation( absoluteIndex: 2, lineIndex: 0, characterIndex: 2); var suffixLocation1 = new SourceLocation( absoluteIndex: 8 + newlineLength + attributeNameLength, lineIndex: 1, characterIndex: 5); var valueLocation1 = new SourceLocation( absoluteIndex: 5 + attributeNameLength + newlineLength, lineIndex: 1, characterIndex: 2); var prefixLocation2 = SourceLocationTracker.Advance(suffixLocation1, "'"); var suffixLocation2 = new SourceLocation( absoluteIndex: 15 + attributeNameLength * 2 + newlineLength * 2, lineIndex: 2, characterIndex: 4); var valueLocation2 = new SourceLocation( absoluteIndex: 12 + attributeNameLength * 2 + newlineLength * 2, lineIndex: 2, characterIndex: 1); // Act & Assert ParseBlockTest( $"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("( $" {attributeName}{Environment.NewLine}='", prefixLocation1), suffix: new LocationTagged("'", suffixLocation1)), Factory.Markup($" {attributeName}{Environment.NewLine}='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, valueLocation1), value: new LocationTagged("Foo", valueLocation1))), Factory.Markup("'").With(SpanChunkGenerator.Null)), new MarkupBlock( new AttributeBlockChunkGenerator( attributeName, prefix: new LocationTagged( $"\t{attributeName}={Environment.NewLine}'", prefixLocation2), suffix: new LocationTagged("'", suffixLocation2)), Factory.Markup($"\t{attributeName}={Environment.NewLine}'").With(SpanChunkGenerator.Null), Factory.Markup("Bar").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, valueLocation2), value: new LocationTagged("Bar", valueLocation2))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Theory] [MemberData(nameof(SymbolBoundAttributeNames))] public void SymbolBoundAttributes_Whitespace(string attributeName) { // Arrange var attributeNameLength = attributeName.Length; var newlineLength = Environment.NewLine.Length; var prefixLocation1 = new SourceLocation( absoluteIndex: 2, lineIndex: 0, characterIndex: 2); var suffixLocation1 = new SourceLocation( absoluteIndex: 10 + newlineLength + attributeNameLength, lineIndex: 1, characterIndex: 7 + attributeNameLength); var valueLocation1 = new SourceLocation( absoluteIndex: 7 + attributeNameLength + newlineLength, lineIndex: 1, characterIndex: 4 + attributeNameLength); var prefixLocation2 = SourceLocationTracker.Advance(suffixLocation1, "'"); var suffixLocation2 = new SourceLocation( absoluteIndex: 17 + attributeNameLength * 2 + newlineLength * 2, lineIndex: 2, characterIndex: 5 + attributeNameLength); var valueLocation2 = new SourceLocation( absoluteIndex: 14 + attributeNameLength * 2 + newlineLength * 2, lineIndex: 2, characterIndex: 2 + attributeNameLength); // Act & Assert ParseBlockTest( $"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("( $" {Environment.NewLine} {attributeName}='", prefixLocation1), suffix: new LocationTagged("'", suffixLocation1)), Factory.Markup($" {Environment.NewLine} {attributeName}='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, valueLocation1), value: new LocationTagged("Foo", valueLocation1))), Factory.Markup("'").With(SpanChunkGenerator.Null)), new MarkupBlock( new AttributeBlockChunkGenerator( attributeName, prefix: new LocationTagged( $"\t{Environment.NewLine}{attributeName}='", prefixLocation2), suffix: new LocationTagged("'", suffixLocation2)), Factory.Markup($"\t{Environment.NewLine}{attributeName}='").With(SpanChunkGenerator.Null), Factory.Markup("Bar").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, valueLocation2), value: new LocationTagged("Bar", valueLocation2))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Theory] [MemberData(nameof(SymbolBoundAttributeNames))] public void SymbolBoundAttributes(string attributeName) { // Arrange var attributeNameLength = attributeName.Length; var suffixLocation = 8 + attributeNameLength; var valueLocation = 5 + attributeNameLength; // Act & Assert ParseBlockTest($"", new MarkupBlock( new MarkupTagBlock( Factory.Markup("($" {attributeName}='", 2, 0, 2), suffix: new LocationTagged("'", suffixLocation, 0, suffixLocation)), Factory.Markup($" {attributeName}='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, valueLocation, 0, valueLocation), value: new LocationTagged("Foo", valueLocation, 0, valueLocation))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void SimpleLiteralAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href='", 2, 0, 2), suffix: new LocationTagged("'", 12, 0, 12)), Factory.Markup(" href='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 9, 0, 9), value: new LocationTagged("Foo", 9, 0, 9))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void SimpleLiteralAttributeWithWhitespaceSurroundingEquals() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href \f\r\n= \t\n'", 2, 0, 2), suffix: new LocationTagged("'", 19, 2, 4)), Factory.Markup(" href \f\r\n= \t\n'").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 16, 2, 1), value: new LocationTagged("Foo", 16, 2, 1))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void DynamicAttributeWithWhitespaceSurroundingEquals() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href \n= \r\n'", 2, 0, 2), suffix: new LocationTagged("'", 18, 2, 5)), Factory.Markup(" href \n= \r\n'").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 14, 2, 1), 14, 2, 1), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("Foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void MultiPartLiteralAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href='", 2, 0, 2), suffix: new LocationTagged("'", 20, 0, 20)), Factory.Markup(" href='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(string.Empty, 9, 0, 9), value: new LocationTagged("Foo", 9, 0, 9))), Factory.Markup(" Bar").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(" ", 12, 0, 12), value: new LocationTagged("Bar", 13, 0, 13))), Factory.Markup(" Baz").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(" ", 16, 0, 16), value: new LocationTagged("Baz", 17, 0, 17))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void DoubleQuotedLiteralAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href=\"", 2, 0, 2), suffix: new LocationTagged("\"", 20, 0, 20)), Factory.Markup(" href=\"").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(string.Empty, 9, 0, 9), value: new LocationTagged("Foo", 9, 0, 9))), Factory.Markup(" Bar").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(" ", 12, 0, 12), value: new LocationTagged("Bar", 13, 0, 13))), Factory.Markup(" Baz").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(" ", 16, 0, 16), value: new LocationTagged("Baz", 17, 0, 17))), Factory.Markup("\"").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void NewLinePrecedingAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("("\r\nhref='", 2, 0, 2), suffix: new LocationTagged("'", 13, 1, 9)), Factory.Markup("\r\nhref='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 10, 1, 6), value: new LocationTagged("Foo", 10, 1, 6))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void NewLineBetweenAttributes() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("("\nhref='", 2, 0, 2), suffix: new LocationTagged("'", 12, 1, 9)), Factory.Markup("\nhref='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 9, 1, 6), value: new LocationTagged("Foo", 9, 1, 6))), Factory.Markup("'").With(SpanChunkGenerator.Null)), new MarkupBlock( new AttributeBlockChunkGenerator( name: "abcd", prefix: new LocationTagged("\r\nabcd='", 13, 1, 10), suffix: new LocationTagged("'", 24, 2, 9)), Factory.Markup("\r\nabcd='").With(SpanChunkGenerator.Null), Factory.Markup("Bar").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 21, 2, 6), value: new LocationTagged("Bar", 21, 2, 6))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void WhitespaceAndNewLinePrecedingAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" \t\r\nhref='", 2, 0, 2), suffix: new LocationTagged("'", 15, 1, 9)), Factory.Markup(" \t\r\nhref='").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With( new LiteralAttributeChunkGenerator( prefix: new LocationTagged(string.Empty, 12, 1, 6), value: new LocationTagged("Foo", 12, 1, 6))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void UnquotedLiteralAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href=", 2, 0, 2), suffix: new LocationTagged(string.Empty, 11, 0, 11)), Factory.Markup(" href=").With(SpanChunkGenerator.Null), Factory.Markup("Foo").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged(string.Empty, 8, 0, 8), value: new LocationTagged("Foo", 8, 0, 8)))), new MarkupBlock(Factory.Markup(" Bar")), new MarkupBlock(Factory.Markup(" Baz")), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void SimpleExpressionAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href='", 2, 0, 2), suffix: new LocationTagged("'", 13, 0, 13)), Factory.Markup(" href='").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void MultiValueExpressionAttribute() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href='", 2, 0, 2), suffix: new LocationTagged("'", 22, 0, 22)), Factory.Markup(" href='").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup(" bar").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 13, 0, 13), new LocationTagged("bar", 14, 0, 14))), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(" ", 17, 0, 17), 18, 0, 18), Factory.Markup(" ").With(SpanChunkGenerator.Null), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("baz") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void VirtualPathAttributesWorkWithConditionalAttributes() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" href='", 2, 0, 2), suffix: new LocationTagged("'", 23, 0, 23)), Factory.Markup(" href='").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 9, 0, 9), 9, 0, 9), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup(" ~/Foo/Bar") .With(new LiteralAttributeChunkGenerator( new LocationTagged(" ", 13, 0, 13), new LocationTagged("~/Foo/Bar", 14, 0, 14))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void UnquotedAttributeWithCodeWithSpacesInBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" value=", 6, 0, 6), suffix: new LocationTagged(string.Empty, 17, 0, 17)), Factory.Markup(" value=").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 13, 0, 13), 13, 0, 13), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace)))), Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void UnquotedAttributeWithCodeWithSpacesInDocument() { ParseDocumentTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" value=", 6, 0, 6), suffix: new LocationTagged(string.Empty, 17, 0, 17)), Factory.Markup(" value=").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 13, 0, 13), 13, 0, 13), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace)))), Factory.Markup(" />")))); } [Fact] public void ConditionalAttributeCollapserDoesNotRewriteEscapedTransitions() { // Act var results = ParseDocument(""); var attributeCollapser = new ConditionalAttributeCollapser(); var rewritten = attributeCollapser.Rewrite(results.Root); // Assert EvaluateParseTree(rewritten, new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" foo='", 5, 0, 5), new LocationTagged("'", 13, 0, 13)), Factory.Markup(" foo='").With(SpanChunkGenerator.Null), new MarkupBlock( Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 11, 0, 11), new LocationTagged("@", 11, 0, 11))).Accepts(AcceptedCharactersInternal.None), Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(" />")))); } [Fact] public void ConditionalAttributesDoNotCreateExtraDataForEntirelyLiteralAttribute() { // Arrange const string code = @"

Title

As the author, you can edit or remove this photo.

Description
The uploader did not provide a description for this photo.
Uploaded by
user.DisplayName
Upload date
photo.UploadDate
Gallery
gallery.Name
Tags
  • This photo has no tags.
edit tags

Download full photo ((photo.FileSize / 1024) KB)

Nobody has commented on this photo

  1. comment.DisplayName commented at comment.CommentDate:

    comment.CommentText

Post new comment

"; // Act var results = ParseDocument(code); var attributeCollapser = new ConditionalAttributeCollapser(); var rewritten = attributeCollapser.Rewrite(results.Root); // Assert Assert.Equal(rewritten.Children.Count(), results.Root.Children.Count()); } [Fact] public void ConditionalAttributesAreEnabledForDataAttributesWithExperimentalFlag() { ParseBlockTest( RazorLanguageVersion.Experimental, "", new MarkupBlock( new MarkupTagBlock( Factory.Markup("(" data-foo='", 5, 0, 5), new LocationTagged("'", 20, 0, 20)), Factory.Markup(" data-foo='").With(SpanChunkGenerator.Null), new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 16, 0, 16), 16, 0, 16), new ExpressionBlock( Factory.CodeTransition(), Factory.Code("foo") .AsImplicitExpression(CSharpCodeParser.DefaultKeywords) .Accepts(AcceptedCharactersInternal.NonWhiteSpace))), Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ConditionalAttributesAreDisabledForDataAttributesInBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ConditionalAttributesWithWeirdSpacingAreDisabledForDataAttributesInBlock() { ParseBlockTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)), new MarkupTagBlock( Factory.Markup("").Accepts(AcceptedCharactersInternal.None)))); } [Fact] public void ConditionalAttributesAreDisabledForDataAttributesInDocument() { ParseDocumentTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("")), new MarkupTagBlock( Factory.Markup("")))); } [Fact] public void ConditionalAttributesWithWeirdSpacingAreDisabledForDataAttributesInDocument() { ParseDocumentTest("", new MarkupBlock( new MarkupTagBlock( Factory.Markup("")), new MarkupTagBlock( Factory.Markup("")))); } private class EmptyTestDocument : ITextDocument { public int Length { get { throw new NotImplementedException(); } } public SourceLocation Location { get { throw new NotImplementedException(); } } public int Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public int Peek() { throw new NotImplementedException(); } public int Read() { throw new NotImplementedException(); } } } }