410 lines
17 KiB
C#
410 lines
17 KiB
C#
// 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.Collections.Generic;
|
|
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
|
using Microsoft.AspNetCore.Razor.Language;
|
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
|
using Microsoft.VisualStudio.Test;
|
|
using Microsoft.VisualStudio.Text;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.VisualStudio.Editor.Razor
|
|
{
|
|
public class RazorSyntaxTreePartialParserTest : PartialParserTestBase
|
|
{
|
|
private const string NewLine = "\r\n";
|
|
|
|
public static TheoryData TagHelperPartialParseRejectData
|
|
{
|
|
get
|
|
{
|
|
return new TheoryData<TestEdit>
|
|
{
|
|
CreateInsertionChange("<p></p>", 2, " "),
|
|
CreateInsertionChange("<p></p>", 6, " "),
|
|
CreateInsertionChange("<p some-attr></p>", 12, " "),
|
|
CreateInsertionChange("<p some-attr></p>", 12, "ibute"),
|
|
CreateInsertionChange("<p some-attr></p>", 2, " before"),
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(TagHelperPartialParseRejectData))]
|
|
public void TagHelperTagBodiesRejectPartialChanges(object objectEdit)
|
|
{
|
|
// Arrange
|
|
var edit = (TestEdit)objectEdit;
|
|
var builder = TagHelperDescriptorBuilder.Create("PTagHelper", "TestAssembly");
|
|
builder.SetTypeName("PTagHelper");
|
|
builder.TagMatchingRule(rule => rule.TagName = "p");
|
|
var descriptors = new[]
|
|
{
|
|
builder.Build()
|
|
};
|
|
var projectEngine = CreateProjectEngine(tagHelpers: descriptors);
|
|
var projectItem = new TestRazorProjectItem("Index.cshtml")
|
|
{
|
|
Content = edit.OldSnapshot.GetText()
|
|
};
|
|
var codeDocument = projectEngine.Process(projectItem);
|
|
var syntaxTree = codeDocument.GetSyntaxTree();
|
|
var parser = new RazorSyntaxTreePartialParser(syntaxTree);
|
|
|
|
// Act
|
|
var result = parser.Parse(edit.Change);
|
|
|
|
// Assert
|
|
Assert.Equal(PartialParseResultInternal.Rejected, result);
|
|
}
|
|
|
|
public static TheoryData TagHelperAttributeAcceptData
|
|
{
|
|
get
|
|
{
|
|
// change, (Block)expectedDocument, partialParseResult
|
|
return new TheoryData<TestEdit, PartialParseResultInternal>
|
|
{
|
|
{
|
|
CreateInsertionChange("<p str-attr='@DateTime'></p>", 22, "."),
|
|
PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional
|
|
},
|
|
{
|
|
CreateInsertionChange("<p obj-attr='DateTime'></p>", 21, "."),
|
|
PartialParseResultInternal.Accepted
|
|
},
|
|
{
|
|
CreateInsertionChange("<p obj-attr='1 + DateTime'></p>", 25, "."),
|
|
PartialParseResultInternal.Accepted
|
|
},
|
|
{
|
|
CreateInsertionChange("<p before-attr str-attr='@DateTime' after-attr></p>", 34, "."),
|
|
PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional
|
|
},
|
|
{
|
|
CreateInsertionChange("<p str-attr='before @DateTime after'></p>", 29, "."),
|
|
PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(TagHelperAttributeAcceptData))]
|
|
public void TagHelperAttributesAreLocatedAndAcceptChangesCorrectly(object editObject, object partialParseResultObject)
|
|
{
|
|
// Arrange
|
|
var edit = (TestEdit)editObject;
|
|
var partialParseResult = (PartialParseResultInternal)partialParseResultObject;
|
|
var builder = TagHelperDescriptorBuilder.Create("PTagHelper", "Test");
|
|
builder.SetTypeName("PTagHelper");
|
|
builder.TagMatchingRule(rule => rule.TagName = "p");
|
|
builder.BindAttribute(attribute =>
|
|
{
|
|
attribute.Name = "obj-attr";
|
|
attribute.TypeName = typeof(object).FullName;
|
|
attribute.SetPropertyName("ObjectAttribute");
|
|
});
|
|
builder.BindAttribute(attribute =>
|
|
{
|
|
attribute.Name = "str-attr";
|
|
attribute.TypeName = typeof(string).FullName;
|
|
attribute.SetPropertyName("StringAttribute");
|
|
});
|
|
var descriptors = new[] { builder.Build() };
|
|
var projectEngine = CreateProjectEngine(tagHelpers: descriptors);
|
|
var sourceDocument = new TestRazorProjectItem("Index.cshtml")
|
|
{
|
|
Content = edit.OldSnapshot.GetText()
|
|
};
|
|
var codeDocument = projectEngine.Process(sourceDocument);
|
|
var syntaxTree = codeDocument.GetSyntaxTree();
|
|
var parser = new RazorSyntaxTreePartialParser(syntaxTree);
|
|
|
|
// Act
|
|
var result = parser.Parse(edit.Change);
|
|
|
|
// Assert
|
|
Assert.Equal(partialParseResult, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsInnerInsertionsInStatementBlock()
|
|
{
|
|
// Arrange
|
|
var changed = new StringTextSnapshot("@{" + NewLine
|
|
+ " @DateTime..Now" + NewLine
|
|
+ "}");
|
|
var old = new StringTextSnapshot("@{" + NewLine
|
|
+ " @DateTime.Now" + NewLine
|
|
+ "}");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(17, 0, old, 1, changed, "."));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsInnerInsertions()
|
|
{
|
|
// Arrange
|
|
var changed = new StringTextSnapshot("foo @DateTime..Now baz");
|
|
var old = new StringTextSnapshot("foo @DateTime.Now baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(13, 0, old, 1, changed, "."), additionalFlags: PartialParseResultInternal.Provisional);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsWholeIdentifierReplacement()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @date baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(5, 4, old, 8, changed, "DateTime"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprRejectsWholeIdentifierReplacementToKeyword()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @date baz");
|
|
var changed = new StringTextSnapshot("foo @if baz");
|
|
var edit = new TestEdit(5, 4, old, 2, changed, "if");
|
|
|
|
// Act & Assert
|
|
RunPartialParseRejectionTest(edit);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprRejectsWholeIdentifierReplacementToDirective()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @date baz");
|
|
var changed = new StringTextSnapshot("foo @inherits baz");
|
|
var edit = new TestEdit(5, 4, old, 8, changed, "inherits");
|
|
|
|
// Act & Assert
|
|
RunPartialParseRejectionTest(edit, PartialParseResultInternal.SpanContextChanged);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsPrefixIdentifierReplacements_SingleSymbol()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @dTime baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(5, 1, old, 4, changed, "Date"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsPrefixIdentifierReplacements_MultipleSymbols()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @dTime.Now baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime.Now baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(5, 1, old, 4, changed, "Date"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsSuffixIdentifierReplacements_SingleSymbol()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @Datet baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(9, 1, old, 4, changed, "Time"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsSuffixIdentifierReplacements_MultipleSymbols()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @DateTime.n baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime.Now baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(14, 1, old, 3, changed, "Now"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsSurroundedIdentifierReplacements()
|
|
{
|
|
// Arrange
|
|
var old = new StringTextSnapshot("foo @DateTime.n.ToString() baz");
|
|
var changed = new StringTextSnapshot("foo @DateTime.Now.ToString() baz");
|
|
|
|
// Act and Assert
|
|
RunPartialParseTest(new TestEdit(14, 1, old, 3, changed, "Now"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprProvisionallyAcceptsDeleteOfIdentifierPartsIfDotRemains()
|
|
{
|
|
var changed = new StringTextSnapshot("foo @User. baz");
|
|
var old = new StringTextSnapshot("foo @User.Name baz");
|
|
RunPartialParseTest(new TestEdit(10, 4, old, 0, changed, string.Empty),
|
|
additionalFlags: PartialParseResultInternal.Provisional);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsDeleteOfIdentifierPartsIfSomeOfIdentifierRemains()
|
|
{
|
|
var changed = new StringTextSnapshot("foo @Us baz");
|
|
var old = new StringTextSnapshot("foo @User baz");
|
|
RunPartialParseTest(new TestEdit(7, 2, old, 0, changed, string.Empty));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprProvisionalForMultipleInsertionIfItCausesIdentifierExpansionAndTrailingDot()
|
|
{
|
|
// ImpExprProvisionallyAcceptsMultipleInsertionIfItCausesIdentifierExpansionAndTrailingDot
|
|
var changed = new StringTextSnapshot("foo @User. baz");
|
|
var old = new StringTextSnapshot("foo @U baz");
|
|
RunPartialParseTest(new TestEdit(6, 0, old, 4, changed, "ser."),
|
|
additionalFlags: PartialParseResultInternal.Provisional);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsMultipleInsertionIfItOnlyCausesIdentifierExpansion()
|
|
{
|
|
var changed = new StringTextSnapshot("foo @barbiz baz");
|
|
var old = new StringTextSnapshot("foo @bar baz");
|
|
RunPartialParseTest(new TestEdit(8, 0, old, 3, changed, "biz"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsIdentifierExpansionAtEndOfNonWhitespaceCharacters()
|
|
{
|
|
var changed = new StringTextSnapshot("@{" + NewLine
|
|
+ " @food" + NewLine
|
|
+ "}");
|
|
var old = new StringTextSnapshot("@{" + NewLine
|
|
+ " @foo" + NewLine
|
|
+ "}");
|
|
RunPartialParseTest(new TestEdit(10 + NewLine.Length, 0, old, 1, changed, "d"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsIdentifierAfterDotAtEndOfNonWhitespaceCharacters()
|
|
{
|
|
var changed = new StringTextSnapshot("@{" + NewLine
|
|
+ " @foo.d" + NewLine
|
|
+ "}");
|
|
var old = new StringTextSnapshot("@{" + NewLine
|
|
+ " @foo." + NewLine
|
|
+ "}");
|
|
RunPartialParseTest(new TestEdit(11 + NewLine.Length, 0, old, 1, changed, "d"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsDotAtEndOfNonWhitespaceCharacters()
|
|
{
|
|
var changed = new StringTextSnapshot("@{" + NewLine
|
|
+ " @foo." + NewLine
|
|
+ "}");
|
|
var old = new StringTextSnapshot("@{" + NewLine
|
|
+ " @foo" + NewLine
|
|
+ "}");
|
|
RunPartialParseTest(new TestEdit(10 + NewLine.Length, 0, old, 1, changed, "."));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprProvisionallyAcceptsDotAfterIdentifierInMarkup()
|
|
{
|
|
var changed = new StringTextSnapshot("foo @foo. bar");
|
|
var old = new StringTextSnapshot("foo @foo bar");
|
|
RunPartialParseTest(new TestEdit(8, 0, old, 1, changed, "."),
|
|
additionalFlags: PartialParseResultInternal.Provisional);
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsAdditionalIdentifierCharactersIfEndOfSpanIsIdentifier()
|
|
{
|
|
var changed = new StringTextSnapshot("foo @foob bar");
|
|
var old = new StringTextSnapshot("foo @foo bar");
|
|
RunPartialParseTest(new TestEdit(8, 0, old, 1, changed, "b"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsAdditionalIdentifierStartCharactersIfEndOfSpanIsDot()
|
|
{
|
|
var changed = new StringTextSnapshot("@{@foo.b}");
|
|
var old = new StringTextSnapshot("@{@foo.}");
|
|
RunPartialParseTest(new TestEdit(7, 0, old, 1, changed, "b"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ImpExprAcceptsDotIfTrailingDotsAreAllowed()
|
|
{
|
|
var changed = new StringTextSnapshot("@{@foo.}");
|
|
var old = new StringTextSnapshot("@{@foo}");
|
|
RunPartialParseTest(new TestEdit(6, 0, old, 1, changed, "."));
|
|
}
|
|
|
|
private void RunPartialParseRejectionTest(TestEdit edit, PartialParseResultInternal additionalFlags = 0)
|
|
{
|
|
var templateEngine = CreateProjectEngine();
|
|
var document = TestRazorCodeDocument.Create(edit.OldSnapshot.GetText());
|
|
templateEngine.Engine.Process(document);
|
|
var syntaxTree = document.GetSyntaxTree();
|
|
var parser = new RazorSyntaxTreePartialParser(syntaxTree);
|
|
|
|
var result = parser.Parse(edit.Change);
|
|
Assert.Equal(PartialParseResultInternal.Rejected | additionalFlags, result);
|
|
}
|
|
|
|
private void RunPartialParseTest(TestEdit edit, PartialParseResultInternal additionalFlags = 0)
|
|
{
|
|
var templateEngine = CreateProjectEngine();
|
|
var document = TestRazorCodeDocument.Create(edit.OldSnapshot.GetText());
|
|
templateEngine.Engine.Process(document);
|
|
var syntaxTree = document.GetSyntaxTree();
|
|
var parser = new RazorSyntaxTreePartialParser(syntaxTree);
|
|
|
|
var result = parser.Parse(edit.Change);
|
|
Assert.Equal(PartialParseResultInternal.Accepted | additionalFlags, result);
|
|
|
|
var newSource = TestRazorSourceDocument.Create(edit.NewSnapshot.GetText());
|
|
var newSyntaxTree = RazorSyntaxTree.Create(parser.ModifiedSyntaxTreeRoot, newSource, parser.OriginalSyntaxTree.Diagnostics, parser.OriginalSyntaxTree.Options);
|
|
BaselineTest(newSyntaxTree);
|
|
}
|
|
|
|
private static TestEdit CreateInsertionChange(string initialText, int insertionLocation, string insertionText)
|
|
{
|
|
var changedText = initialText.Insert(insertionLocation, insertionText);
|
|
var sourceChange = new SourceChange(insertionLocation, 0, insertionText);
|
|
var oldSnapshot = new StringTextSnapshot(initialText);
|
|
var changedSnapshot = new StringTextSnapshot(changedText);
|
|
return new TestEdit(sourceChange, oldSnapshot, changedSnapshot);
|
|
}
|
|
|
|
private static RazorProjectEngine CreateProjectEngine(
|
|
string path = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml",
|
|
IEnumerable<TagHelperDescriptor> tagHelpers = null)
|
|
{
|
|
var fileSystem = new TestRazorProjectFileSystem();
|
|
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
|
|
{
|
|
RazorExtensions.Register(builder);
|
|
|
|
builder.AddDefaultImports("@addTagHelper *, Test");
|
|
|
|
if (tagHelpers != null)
|
|
{
|
|
builder.AddTagHelpers(tagHelpers);
|
|
}
|
|
});
|
|
|
|
return projectEngine;
|
|
}
|
|
}
|
|
}
|