aspnetcore/test/Microsoft.VisualStudio.Lang.../RazorEditorParserTest.cs

1479 lines
67 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.VisualStudio.Text;
using Xunit;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public class RazorEditorParserTest
{
private static readonly TestFile SimpleCSHTMLDocument = TestFile.Create("TestFiles/DesignTime/Simple.cshtml", typeof(RazorEditorParserTest));
private static readonly TestFile SimpleCSHTMLDocumentGenerated = TestFile.Create("TestFiles/DesignTime/Simple.txt", typeof(RazorEditorParserTest));
private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
public static TheoryData TagHelperPartialParseRejectData
{
get
{
// change, (Block)expectedDocument
return new TheoryData<TestEdit, MarkupBlock>
{
{
CreateInsertionChange("<p></p>", 2, " "),
new MarkupBlock(
new MarkupTagHelperBlock("p"))
},
{
CreateInsertionChange("<p></p>", 6, " "),
new MarkupBlock(
new MarkupTagHelperBlock("p"))
},
{
CreateInsertionChange("<p some-attr></p>", 12, " "),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"some-attr",
value: null,
attributeStructure: AttributeStructure.Minimized)
}))
},
{
CreateInsertionChange("<p some-attr></p>", 12, "ibute"),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"some-attribute",
value: null,
attributeStructure: AttributeStructure.Minimized)
}))
},
{
CreateInsertionChange("<p some-attr></p>", 2, " before"),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"before",
value: null,
attributeStructure: AttributeStructure.Minimized),
new TagHelperAttributeNode(
"some-attr",
value: null,
attributeStructure: AttributeStructure.Minimized)
}))
},
};
}
}
[Theory]
[MemberData(nameof(TagHelperPartialParseRejectData))]
public void TagHelperTagBodiesRejectPartialChanges(object editObject, object expectedDocument)
{
// Arrange
var edit = (TestEdit)editObject;
var builder = TagHelperDescriptorBuilder.Create("PTagHelper", "TestAssembly");
builder.SetTypeName("PTagHelper");
builder.TagMatchingRule(rule => rule.TagName = "p");
var descriptors = new[]
{
builder.Build()
};
var parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path"), @"C:\This\Is\A\Test\Path");
using (var manager = new TestParserManager(parser))
{
manager.InitializeWithDocument(edit.OldSnapshot);
// Act
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(PartialParseResult.Rejected, result);
Assert.Equal(2, manager.ParseCount);
}
}
public static TheoryData TagHelperAttributeAcceptData
{
get
{
var factory = new SpanFactory();
// change, (Block)expectedDocument, partialParseResult
return new TheoryData<TestEdit, MarkupBlock, PartialParseResult>
{
{
CreateInsertionChange("<p str-attr='@DateTime'></p>", 22, "."),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"str-attr",
new MarkupBlock(
new MarkupBlock(
new ExpressionBlock(
factory.CodeTransition(),
factory
.Code("DateTime.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)))),
AttributeStructure.SingleQuotes)
})),
PartialParseResult.Accepted | PartialParseResult.Provisional
},
{
CreateInsertionChange("<p obj-attr='DateTime'></p>", 21, "."),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"obj-attr",
factory.CodeMarkup("DateTime."),
AttributeStructure.SingleQuotes)
})),
PartialParseResult.Accepted
},
{
CreateInsertionChange("<p obj-attr='1 + DateTime'></p>", 25, "."),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"obj-attr",
factory.CodeMarkup("1 + DateTime."),
AttributeStructure.SingleQuotes)
})),
PartialParseResult.Accepted
},
{
CreateInsertionChange("<p before-attr str-attr='@DateTime' after-attr></p>", 34, "."),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"before-attr",
value: null,
attributeStructure: AttributeStructure.Minimized),
new TagHelperAttributeNode(
"str-attr",
new MarkupBlock(
new MarkupBlock(
new ExpressionBlock(
factory.CodeTransition(),
factory
.Code("DateTime.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)))),
AttributeStructure.SingleQuotes),
new TagHelperAttributeNode(
"after-attr",
value: null,
attributeStructure: AttributeStructure.Minimized),
})),
PartialParseResult.Accepted | PartialParseResult.Provisional
},
{
CreateInsertionChange("<p str-attr='before @DateTime after'></p>", 29, "."),
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
attributes: new List<TagHelperAttributeNode>
{
new TagHelperAttributeNode(
"str-attr",
new MarkupBlock(
factory.Markup("before"),
new MarkupBlock(
factory.Markup(" "),
new ExpressionBlock(
factory.CodeTransition(),
factory
.Code("DateTime.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace))),
factory.Markup(" after")),
AttributeStructure.SingleQuotes)
})),
PartialParseResult.Accepted | PartialParseResult.Provisional
},
};
}
}
[Theory]
[MemberData(nameof(TagHelperAttributeAcceptData))]
public void TagHelperAttributesAreLocatedAndAcceptChangesCorrectly(
object editObject,
object expectedDocument,
PartialParseResult partialParseResult)
{
// Arrange
var edit = (TestEdit)editObject;
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 parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path", descriptors), @"C:\This\Is\A\Test\Path");
using (var manager = new TestParserManager(parser))
{
manager.InitializeWithDocument(edit.OldSnapshot);
// Act
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(partialParseResult, result);
Assert.Equal(1, manager.ParseCount);
}
}
[Fact]
public void ConstructorRequiresNonNullPhysicalPath()
{
Assert.Throws<ArgumentException>("filePath", () => new RazorEditorParser(CreateTemplateEngine(), null));
}
[Fact]
public void ConstructorRequiresNonEmptyPhysicalPath()
{
Assert.Throws<ArgumentException>("filePath", () => new RazorEditorParser(CreateTemplateEngine(), string.Empty));
}
[Theory]
[InlineData(" ")]
[InlineData("\r\n")]
[InlineData("abcdefg")]
[InlineData("\f\r\n abcd \t")]
public void TreesAreDifferentReturnsFalseForAddedContent(string content)
{
// Arrange
var factory = new SpanFactory();
var blockFactory = new BlockFactory(factory);
var original = new MarkupBlock(
blockFactory.MarkupTagBlock("<p>"),
blockFactory.TagHelperBlock(
tagName: "div",
tagMode: TagMode.StartTagAndEndTag,
start: new SourceLocation(3, 0, 3),
startTag: blockFactory.MarkupTagBlock("<div>"),
children: new SyntaxTreeNode[]
{
factory.Markup($"{Environment.NewLine}{Environment.NewLine}")
},
endTag: blockFactory.MarkupTagBlock("</div>")),
blockFactory.MarkupTagBlock("</p>"));
factory.Reset();
var modified = new MarkupBlock(
blockFactory.MarkupTagBlock("<p>"),
blockFactory.TagHelperBlock(
tagName: "div",
tagMode: TagMode.StartTagAndEndTag,
start: new SourceLocation(3, 0, 3),
startTag: blockFactory.MarkupTagBlock("<div>"),
children: new SyntaxTreeNode[]
{
factory.Markup($"{Environment.NewLine}{content}{Environment.NewLine}")
},
endTag: blockFactory.MarkupTagBlock("</div>")),
blockFactory.MarkupTagBlock("</p>"));
original.LinkNodes();
modified.LinkNodes();
// Act
var treesAreDifferent = BackgroundParser.TreesAreDifferent(
original,
modified,
new[]
{
new SourceChange(
absoluteIndex: 8 + Environment.NewLine.Length,
length: 0,
newText: content)
},
CancellationToken.None);
// Assert
Assert.False(treesAreDifferent);
}
[Fact]
public void TreesAreDifferentReturnsTrueIfTreeStructureIsDifferent()
{
var factory = new SpanFactory();
var original = new MarkupBlock(
factory.Markup("<p>"),
new ExpressionBlock(
factory.CodeTransition()),
factory.Markup("</p>"));
var modified = new MarkupBlock(
factory.Markup("<p>"),
new ExpressionBlock(
factory.CodeTransition("@"),
factory.Code("f")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
factory.Markup("</p>"));
Assert.True(BackgroundParser.TreesAreDifferent(
original,
modified,
new[]
{
new SourceChange(absoluteIndex: 4, length: 0, newText: "f")
},
CancellationToken.None));
}
[Fact]
public void TreesAreDifferentReturnsFalseIfTreeStructureIsSame()
{
var factory = new SpanFactory();
var original = new MarkupBlock(
factory.Markup("<p>"),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("f")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
factory.Markup("</p>"));
factory.Reset();
var modified = new MarkupBlock(
factory.Markup("<p>"),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false)),
factory.Markup("</p>"));
original.LinkNodes();
modified.LinkNodes();
Assert.False(BackgroundParser.TreesAreDifferent(
original,
modified,
new[]
{
new SourceChange(absoluteIndex: 5, length: 0, newText: "oo")
},
CancellationToken.None));
}
[Fact]
public void CheckForStructureChangesStartsFullReparseIfChangeOverlapsMultipleSpans()
{
// Arrange
using (var parser = new RazorEditorParser(CreateTemplateEngine(), TestLinePragmaFileName))
{
var original = new StringTextSnapshot("Foo @bar Baz");
var changed = new StringTextSnapshot("Foo @bap Daz");
var change = new SourceChange(7, 3, "p D");
var parseComplete = new ManualResetEventSlim();
var parseCount = 0;
parser.DocumentParseComplete += (sender, args) =>
{
Interlocked.Increment(ref parseCount);
parseComplete.Set();
};
Assert.Equal(PartialParseResult.Rejected, parser.CheckForStructureChanges(change, original));
DoWithTimeoutIfNotDebugging(parseComplete.Wait); // Wait for the parse to finish
parseComplete.Reset();
// Act
var result = parser.CheckForStructureChanges(change, original);
// Assert
Assert.Equal(PartialParseResult.Rejected, result);
DoWithTimeoutIfNotDebugging(parseComplete.Wait);
Assert.Equal(2, parseCount);
}
}
[Fact]
public void AwaitPeriodInsertionAcceptedProvisionally()
{
// Arrange
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @await Html. baz");
var old = new StringTextSnapshot("foo @await Html baz");
// Act and Assert
RunPartialParseTest(new TestEdit(15, 0, old, 1, changed, "."),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("await Html.").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.WhiteSpace | AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")), additionalFlags: PartialParseResult.Provisional);
}
[Fact]
public void ImplicitExpressionAcceptsInnerInsertionsInStatementBlock()
{
// Arrange
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime..Now" + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime.Now" + Environment.NewLine
+ "}");
// Act and Assert
RunPartialParseTest(new TestEdit(17, 0, old, 1, changed, "."),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime..Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionAcceptsInnerInsertions()
{
// Arrange
var factory = new SpanFactory();
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, "."),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime..Now").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")), additionalFlags: PartialParseResult.Provisional);
}
[Fact]
public void ImplicitExpressionAcceptsWholeIdentifierReplacement()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionRejectsWholeIdentifierReplacementToKeyword()
{
// Arrange
var parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path"), @"C:\This\Is\A\Test\Path");
using (var manager = new TestParserManager(parser))
{
var old = new StringTextSnapshot("foo @date baz");
var changed = new StringTextSnapshot("foo @if baz");
var edit = new TestEdit(5, 4, old, 2, changed, "if");
manager.InitializeWithDocument(old);
// Act
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(PartialParseResult.Rejected, result);
Assert.Equal(2, manager.ParseCount);
}
}
[Fact]
public void ImplicitExpressionRejectsWholeIdentifierReplacementToDirective()
{
// Arrange
var parser = new RazorEditorParser(CreateTemplateEngine(@"C:\This\Is\A\Test\Path"), @"C:\This\Is\A\Test\Path");
using (var manager = new TestParserManager(parser))
{
var old = new StringTextSnapshot("foo @date baz");
var changed = new StringTextSnapshot("foo @inherits baz");
var SourceChange = new TestEdit(5, 4, old, 8, changed, "inherits");
manager.InitializeWithDocument(old);
// Act
var result = manager.CheckForStructureChangesAndWait(SourceChange);
// Assert
Assert.Equal(PartialParseResult.Rejected | PartialParseResult.SpanContextChanged, result);
Assert.Equal(2, manager.ParseCount);
}
}
[Fact]
public void ImplicitExpressionAcceptsPrefixIdentifierReplacements_SingleSymbol()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsPrefixIdentifierReplacements_MultipleSymbols()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsSuffixIdentifierReplacements_SingleSymbol()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsSuffixIdentifierReplacements_MultipleSymbols()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsSurroundedIdentifierReplacements()
{
// Arrange
var factory = new SpanFactory();
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"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now.ToString()").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime." + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime" + Environment.NewLine
+ "}");
var edit = new TestEdit(15 + Environment.NewLine.Length, 0, old, 1, changed, ".");
using (var manager = CreateParserManager())
{
Action<TestEdit, PartialParseResult, string> applyAndVerifyPartialChange = (changeToApply, expectedResult, expectedCode) =>
{
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
};
manager.InitializeWithDocument(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateTime.");
old = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime.." + Environment.NewLine
+ "}");
edit = new TestEdit(16 + Environment.NewLine.Length, 0, old, 1, changed, ".");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateTime..");
old = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime.Now." + Environment.NewLine
+ "}");
edit = new TestEdit(16 + Environment.NewLine.Length, 0, old, 3, changed, "Now");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateTime.Now.");
}
}
[Fact]
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateT." + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateT" + Environment.NewLine
+ "}");
var edit = new TestEdit(12 + Environment.NewLine.Length, 0, old, 1, changed, ".");
using (var manager = CreateParserManager())
{
Action<TestEdit, PartialParseResult, string> applyAndVerifyPartialChange = (changeToApply, expectedResult, expectedCode) =>
{
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
};
manager.InitializeWithDocument(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateT.");
old = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime." + Environment.NewLine
+ "}");
edit = new TestEdit(12 + Environment.NewLine.Length, 0, old, 3, changed, "ime");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateTime.");
}
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertions()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @DateT. baz");
var old = new StringTextSnapshot("foo @DateT baz");
var edit = new TestEdit(10, 0, old, 1, changed, ".");
using (var manager = CreateParserManager())
{
Action<TestEdit, PartialParseResult, string> applyAndVerifyPartialChange = (changeToApply, expectedResult, expectedCode) =>
{
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
manager.InitializeWithDocument(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateT.");
old = changed;
changed = new StringTextSnapshot("foo @DateTime. baz");
edit = new TestEdit(10, 0, old, 3, changed, "ime");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateTime.");
}
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertionsAfterIdentifiers()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @DateTime. baz");
var old = new StringTextSnapshot("foo @DateTime baz");
var edit = new TestEdit(13, 0, old, 1, changed, ".");
using (var manager = CreateParserManager())
{
Action<TestEdit, PartialParseResult, string> applyAndVerifyPartialChange = (changeToApply, expectedResult, expectedCode) =>
{
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
manager.InitializeWithDocument(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateTime.");
old = changed;
changed = new StringTextSnapshot("foo @DateTime.. baz");
edit = new TestEdit(14, 0, old, 1, changed, ".");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateTime..");
old = changed;
changed = new StringTextSnapshot("foo @DateTime.Now. baz");
edit = new TestEdit(14, 0, old, 3, changed, "Now");
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateTime.Now.");
}
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsCaseInsensitiveDotlessCommitInsertions_NewRoslynIntegration()
{
var factory = new SpanFactory();
var old = new StringTextSnapshot("foo @date baz");
var changed = new StringTextSnapshot("foo @date. baz");
var edit = new TestEdit(9, 0, old, 1, changed, ".");
using (var manager = CreateParserManager())
{
Action<TestEdit, PartialParseResult, string> applyAndVerifyPartialChange = (changeToApply, expectedResult, expectedCode) =>
{
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
manager.InitializeWithDocument(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
// @date => @date.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "date.");
old = changed;
changed = new StringTextSnapshot("foo @date baz");
edit = new TestEdit(9, 1, old, 0, changed, "");
// @date. => @date
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "date");
old = changed;
changed = new StringTextSnapshot("foo @DateTime baz");
edit = new TestEdit(5, 4, old, 8, changed, "DateTime");
// @date => @DateTime
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted, "DateTime");
old = changed;
changed = new StringTextSnapshot("foo @DateTime. baz");
edit = new TestEdit(13, 0, old, 1, changed, ".");
// @DateTime => @DateTime.
applyAndVerifyPartialChange(edit, PartialParseResult.Accepted | PartialParseResult.Provisional, "DateTime.");
}
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsDeleteOfIdentifierPartsIfDotRemains()
{
var factory = new SpanFactory();
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),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("User.").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")),
additionalFlags: PartialParseResult.Provisional);
}
[Fact]
public void ImplicitExpressionAcceptsDeleteOfIdentifierPartsIfSomeOfIdentifierRemains()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @Us baz");
var old = new StringTextSnapshot("foo @User baz");
RunPartialParseTest(new TestEdit(7, 2, old, 0, changed, string.Empty),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("Us").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsMultipleInsertionIfItCausesIdentifierExpansionAndTrailingDot()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @User. baz");
var old = new StringTextSnapshot("foo @U baz");
RunPartialParseTest(new TestEdit(6, 0, old, 4, changed, "ser."),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("User.").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")),
additionalFlags: PartialParseResult.Provisional);
}
[Fact]
public void ImplicitExpressionAcceptsMultipleInsertionIfItOnlyCausesIdentifierExpansion()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @barbiz baz");
var old = new StringTextSnapshot("foo @bar baz");
RunPartialParseTest(new TestEdit(8, 0, old, 3, changed, "biz"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("barbiz").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
}
[Fact]
public void ImplicitExpressionAcceptsIdentifierExpansionAtEndOfNonWhitespaceCharacters()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @food" + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @foo" + Environment.NewLine
+ "}");
RunPartialParseTest(new TestEdit(10 + Environment.NewLine.Length, 0, old, 1, changed, "d"),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("food")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionAcceptsIdentifierAfterDotAtEndOfNonWhitespaceCharacters()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @foo.d" + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @foo." + Environment.NewLine
+ "}");
RunPartialParseTest(new TestEdit(11 + Environment.NewLine.Length, 0, old, 1, changed, "d"),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.d")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionAcceptsDotAtEndOfNonWhitespaceCharacters()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @foo." + Environment.NewLine
+ "}");
var old = new StringTextSnapshot("@{" + Environment.NewLine
+ " @foo" + Environment.NewLine
+ "}");
RunPartialParseTest(new TestEdit(10 + Environment.NewLine.Length, 0, old, 1, changed, "."),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(@"foo.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
{
var factory = new SpanFactory();
// Arrange
var dotTyped = new TestEdit(8, 0, new StringTextSnapshot("foo @foo @bar"), 1, new StringTextSnapshot("foo @foo. @bar"), ".");
var charTyped = new TestEdit(14, 0, new StringTextSnapshot("foo @foo. @bar"), 1, new StringTextSnapshot("foo @foo. @barb"), "b");
using (var manager = CreateParserManager())
{
manager.InitializeWithDocument(dotTyped.OldSnapshot);
// Apply the dot change
Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
// Act (apply the identifier start char change)
var result = manager.CheckForStructureChangesAndWait(charTyped);
// Assert
Assert.Equal(PartialParseResult.Rejected, result);
Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root,
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("barb")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.EmptyHtml()));
}
}
[Fact]
public void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
{
var factory = new SpanFactory();
// Arrange
var dotTyped = new TestEdit(8, 0, new StringTextSnapshot("foo @foo bar"), 1, new StringTextSnapshot("foo @foo. bar"), ".");
var charTyped = new TestEdit(9, 0, new StringTextSnapshot("foo @foo. bar"), 1, new StringTextSnapshot("foo @foo.b bar"), "b");
using (var manager = CreateParserManager())
{
manager.InitializeWithDocument(dotTyped.OldSnapshot);
// Apply the dot change
Assert.Equal(PartialParseResult.Provisional | PartialParseResult.Accepted, manager.CheckForStructureChangesAndWait(dotTyped));
// Act (apply the identifier start char change)
var result = manager.CheckForStructureChangesAndWait(charTyped);
// Assert
Assert.Equal(PartialParseResult.Accepted, result);
Assert.False(manager.Parser.LastResultProvisional, "LastResultProvisional flag should have been cleared but it was not");
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root,
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.b")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" bar")));
}
}
[Fact]
public void ImplicitExpressionProvisionallyAcceptsDotAfterIdentifierInMarkup()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @foo. bar");
var old = new StringTextSnapshot("foo @foo bar");
RunPartialParseTest(new TestEdit(8, 0, old, 1, changed, "."),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" bar")),
additionalFlags: PartialParseResult.Provisional);
}
[Fact]
public void ImplicitExpressionAcceptsAdditionalIdentifierCharactersIfEndOfSpanIsIdentifier()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @foob bar");
var old = new StringTextSnapshot("foo @foo bar");
RunPartialParseTest(new TestEdit(8, 0, old, 1, changed, "b"),
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foob")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" bar")));
}
[Fact]
public void ImplicitExpressionAcceptsAdditionalIdentifierStartCharactersIfEndOfSpanIsDot()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{@foo.b}");
var old = new StringTextSnapshot("@{@foo.}");
RunPartialParseTest(new TestEdit(7, 0, old, 1, changed, "b"),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.EmptyCSharp()
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.b")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.EmptyCSharp().AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionAcceptsDotIfTrailingDotsAreAllowed()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{@foo.}");
var old = new StringTextSnapshot("@{@foo}");
RunPartialParseTest(new TestEdit(6, 0, old, 1, changed, "."),
new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.EmptyCSharp()
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.EmptyCSharp().AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
{
RunTypeKeywordTest("if");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
{
RunTypeKeywordTest("do");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
{
RunTypeKeywordTest("try");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
{
RunTypeKeywordTest("for");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
{
RunTypeKeywordTest("foreach");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
{
RunTypeKeywordTest("while");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
{
RunTypeKeywordTest("switch");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
{
RunTypeKeywordTest("lock");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
{
RunTypeKeywordTest("using");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
{
RunTypeKeywordTest("section");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
{
RunTypeKeywordTest("inherits");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
{
RunTypeKeywordTest("functions");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
{
RunTypeKeywordTest("namespace");
}
[Fact]
public void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
{
RunTypeKeywordTest("class");
}
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
{
Change = sourceChange,
OldSnapshot = oldSnapshot,
NewSnapshot = changedSnapshot,
};
}
private static void RunFullReparseTest(TestEdit edit, PartialParseResult additionalFlags = (PartialParseResult)0)
{
// Arrange
using (var manager = CreateParserManager())
{
manager.InitializeWithDocument(edit.OldSnapshot);
// Act
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(PartialParseResult.Rejected | additionalFlags, result);
Assert.Equal(2, manager.ParseCount);
}
}
private static void RunPartialParseTest(TestEdit edit, Block newTreeRoot, PartialParseResult additionalFlags = (PartialParseResult)0)
{
// Arrange
using (var manager = CreateParserManager())
{
manager.InitializeWithDocument(edit.OldSnapshot);
// Act
var result = manager.CheckForStructureChangesAndWait(edit);
// Assert
Assert.Equal(PartialParseResult.Accepted | additionalFlags, result);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentSyntaxTree.Root, newTreeRoot);
}
}
private static TestParserManager CreateParserManager()
{
var parser = new RazorEditorParser(CreateTemplateEngine(), TestLinePragmaFileName);
return new TestParserManager(parser);
}
private static RazorTemplateEngine CreateTemplateEngine(
string path = TestLinePragmaFileName,
IEnumerable<TagHelperDescriptor> tagHelpers = null)
{
var engine = RazorEngine.CreateDesignTime(builder =>
{
RazorExtensions.Register(builder);
if (tagHelpers != null)
{
builder.AddTagHelpers(tagHelpers);
}
});
// GetImports on RazorTemplateEngine will at least check that the item exists, so we need to pretend
// that it does.
var items = new List<RazorProjectItem>();
items.Add(new TestRazorProjectItem(path));
var project = new TestRazorProject(items);
var templateEngine = new RazorTemplateEngine(engine, project);
templateEngine.Options.DefaultImports = RazorSourceDocument.Create("@addTagHelper *, Test", "_TestImports.cshtml");
return templateEngine;
}
private static void RunTypeKeywordTest(string keyword)
{
var before = "@" + keyword.Substring(0, keyword.Length - 1);
var after = "@" + keyword;
var changed = new StringTextSnapshot(after);
var old = new StringTextSnapshot(before);
var change = new SourceChange(keyword.Length, 0, keyword[keyword.Length - 1].ToString());
var edit = new TestEdit
{
Change = change,
NewSnapshot = changed,
OldSnapshot = old
};
RunFullReparseTest(edit, additionalFlags: PartialParseResult.SpanContextChanged);
}
private static void DoWithTimeoutIfNotDebugging(Func<int, bool> withTimeout)
{
#if DEBUG
if (Debugger.IsAttached)
{
withTimeout(Timeout.Infinite);
}
else
{
#endif
Assert.True(withTimeout((int)TimeSpan.FromSeconds(1).TotalMilliseconds), "Timeout expired!");
#if DEBUG
}
#endif
}
private class TestParserManager : IDisposable
{
public int ParseCount;
private readonly ManualResetEventSlim _parserComplete;
public TestParserManager(RazorEditorParser parser)
{
_parserComplete = new ManualResetEventSlim();
ParseCount = 0;
Parser = parser;
parser.DocumentParseComplete += (sender, args) =>
{
Interlocked.Increment(ref ParseCount);
_parserComplete.Set();
};
}
public RazorEditorParser Parser { get; }
public void InitializeWithDocument(ITextSnapshot snapshot)
{
var initialChange = new SourceChange(0, 0, string.Empty);
var edit = new TestEdit
{
Change = initialChange,
OldSnapshot = snapshot,
NewSnapshot = snapshot
};
CheckForStructureChangesAndWait(edit);
}
public PartialParseResult CheckForStructureChangesAndWait(TestEdit edit)
{
var result = Parser.CheckForStructureChanges(edit.Change, edit.NewSnapshot);
if (result.HasFlag(PartialParseResult.Rejected))
{
WaitForParse();
}
return result;
}
public void WaitForParse()
{
DoWithTimeoutIfNotDebugging(_parserComplete.Wait); // Wait for the parse to finish
_parserComplete.Reset();
}
public void Dispose()
{
Parser.Dispose();
}
}
private class TestEdit
{
public TestEdit()
{
}
public TestEdit(int position, int oldLength, ITextSnapshot oldSnapshot, int newLength, ITextSnapshot newSnapshot, string newText)
{
Change = new SourceChange(position, oldLength, newText);
OldSnapshot = oldSnapshot;
NewSnapshot = newSnapshot;
}
public SourceChange Change { get; set; }
public ITextSnapshot OldSnapshot { get; set; }
public ITextSnapshot NewSnapshot { get; set; }
}
}
}