Add legacy parser

This commit is contained in:
N. Taylor Mullen 2016-10-13 15:12:07 -07:00
parent 6b9b75841f
commit d06e5b6002
138 changed files with 25938 additions and 0 deletions

View File

@ -0,0 +1,21 @@
// 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.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultRazorSyntaxTree : RazorSyntaxTree
{
public DefaultRazorSyntaxTree(Block root, IReadOnlyList<RazorError> diagnostics)
{
Root = root;
Diagnostics = diagnostics;
}
internal override IReadOnlyList<RazorError> Diagnostics { get; }
internal override Block Root { get; }
}
}

View File

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
[Flags]
internal enum AcceptedCharacters
{
None = 0,
NewLine = 1,
WhiteSpace = 2,
NonWhiteSpace = 4,
AllWhiteSpace = NewLine | WhiteSpace,
Any = AllWhiteSpace | NonWhiteSpace,
AnyExceptNewline = NonWhiteSpace | WhiteSpace
}
}

View File

@ -0,0 +1,47 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class AddImportChunkGenerator : SpanChunkGenerator
{
public AddImportChunkGenerator(string ns)
{
Namespace = ns;
}
public string Namespace { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
var ns = Namespace;
if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
{
ns = ns.Substring(1);
}
//context.ChunkTreeBuilder.AddUsingChunk(ns, target);
}
public override string ToString()
{
return "Import:" + Namespace + ";";
}
public override bool Equals(object obj)
{
var other = obj as AddImportChunkGenerator;
return other != null &&
string.Equals(Namespace, other.Namespace, StringComparison.Ordinal);
}
public override int GetHashCode()
{
// Hash code should include only immutable properties.
return Namespace == null ? 0 : StringComparer.Ordinal.GetHashCode(Namespace);
}
}
}

View File

@ -0,0 +1,41 @@
// 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 Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class AddTagHelperChunkGenerator : SpanChunkGenerator
{
public AddTagHelperChunkGenerator(string lookupText)
{
LookupText = lookupText;
}
public string LookupText { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
var other = obj as AddTagHelperChunkGenerator;
return base.Equals(other) &&
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal);
}
/// <inheritdoc />
public override int GetHashCode()
{
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(LookupText, StringComparer.Ordinal);
return combiner.CombinedHash;
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class AttributeBlockChunkGenerator : ParentChunkGenerator
{
public AttributeBlockChunkGenerator(string name, LocationTagged<string> prefix, LocationTagged<string> suffix)
{
Name = name;
Prefix = prefix;
Suffix = suffix;
}
public string Name { get; }
public LocationTagged<string> Prefix { get; }
public LocationTagged<string> Suffix { get; }
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//var chunk = context.ChunkTreeBuilder.StartParentChunk<CodeAttributeChunk>(target);
//chunk.Attribute = Name;
//chunk.Prefix = Prefix;
//chunk.Suffix = Suffix;
}
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.EndParentChunk();
}
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
}
public override bool Equals(object obj)
{
var other = obj as AttributeBlockChunkGenerator;
return other != null &&
string.Equals(other.Name, Name, StringComparison.Ordinal) &&
Equals(other.Prefix, Prefix) &&
Equals(other.Suffix, Suffix);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Name, StringComparer.Ordinal);
hashCodeCombiner.Add(Prefix);
hashCodeCombiner.Add(Suffix);
return hashCodeCombiner;
}
}
}

View File

@ -0,0 +1,69 @@
// 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 Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class AutoCompleteEditHandler : SpanEditHandler
{
private static readonly int TypeHashCode = typeof(AutoCompleteEditHandler).GetHashCode();
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: base(tokenizer)
{
}
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, bool autoCompleteAtEndOfSpan)
: this(tokenizer)
{
AutoCompleteAtEndOfSpan = autoCompleteAtEndOfSpan;
}
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
: base(tokenizer, accepted)
{
}
public bool AutoCompleteAtEndOfSpan { get; }
public string AutoCompleteString { get; set; }
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) &&
normalizedChange.IsInsert &&
ParserHelpers.IsNewLine(normalizedChange.NewText) &&
AutoCompleteString != null)
{
return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
}
return PartialParseResult.Rejected;
}
public override string ToString()
{
return base.ToString() + ",AutoComplete:[" + (AutoCompleteString ?? "<null>") + "]" + (AutoCompleteAtEndOfSpan ? ";AtEnd" : ";AtEOL");
}
public override bool Equals(object obj)
{
var other = obj as AutoCompleteEditHandler;
return base.Equals(other) &&
string.Equals(other.AutoCompleteString, AutoCompleteString, StringComparison.Ordinal) &&
AutoCompleteAtEndOfSpan == other.AutoCompleteAtEndOfSpan;
}
public override int GetHashCode()
{
// Hash code should include only immutable properties but Equals also checks the type.
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(TypeHashCode);
hashCodeCombiner.Add(AutoCompleteAtEndOfSpan);
return hashCodeCombiner.CombinedHash;
}
}
}

View File

@ -0,0 +1,17 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
[Flags]
internal enum BalancingModes
{
None = 0,
BacktrackOnFailure = 1,
NoErrorOnFailure = 2,
AllowCommentsAndTemplates = 4,
AllowEmbeddedTransitions = 8
}
}

View File

@ -0,0 +1,188 @@
// 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.Globalization;
using System.Linq;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class Block : SyntaxTreeNode
{
public Block(BlockBuilder source)
: this(source.Type, source.Children, source.ChunkGenerator)
{
source.Reset();
}
protected Block(BlockType? type, IReadOnlyList<SyntaxTreeNode> children, IParentChunkGenerator generator)
{
if (type == null)
{
throw new InvalidOperationException(LegacyResources.Block_Type_Not_Specified);
}
Type = type.Value;
Children = children;
ChunkGenerator = generator;
// Perf: Avoid allocating an enumerator.
for (var i = 0; i < Children.Count; i++)
{
Children[i].Parent = this;
}
}
public IParentChunkGenerator ChunkGenerator { get; }
public BlockType Type { get; }
public IReadOnlyList<SyntaxTreeNode> Children { get; }
public override bool IsBlock => true;
public override SourceLocation Start
{
get
{
var child = Children.FirstOrDefault();
if (child == null)
{
return SourceLocation.Zero;
}
else
{
return child.Start;
}
}
}
public override int Length => Children.Sum(child => child.Length);
public virtual IEnumerable<Span> Flatten()
{
// Perf: Avoid allocating an enumerator.
for (var i = 0; i < Children.Count; i++)
{
var element = Children[i];
var span = element as Span;
if (span != null)
{
yield return span;
}
else
{
var block = element as Block;
foreach (Span childSpan in block.Flatten())
{
yield return childSpan;
}
}
}
}
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"{0} Block at {1}::{2} (Gen:{3})",
Type,
Start,
Length,
ChunkGenerator);
}
public override bool Equals(object obj)
{
var other = obj as Block;
return other != null &&
Type == other.Type &&
Equals(ChunkGenerator, other.ChunkGenerator) &&
ChildrenEqual(Children, other.Children);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Type);
hashCodeCombiner.Add(ChunkGenerator);
hashCodeCombiner.Add(Children);
return hashCodeCombiner;
}
private static bool ChildrenEqual(IEnumerable<SyntaxTreeNode> left, IEnumerable<SyntaxTreeNode> right)
{
IEnumerator<SyntaxTreeNode> leftEnum = left.GetEnumerator();
IEnumerator<SyntaxTreeNode> rightEnum = right.GetEnumerator();
while (leftEnum.MoveNext())
{
if (!rightEnum.MoveNext() || // More items in left than in right
!Equals(leftEnum.Current, rightEnum.Current))
{
// Nodes are not equal
return false;
}
}
if (rightEnum.MoveNext())
{
// More items in right than left
return false;
}
return true;
}
public override bool EquivalentTo(SyntaxTreeNode node)
{
var other = node as Block;
if (other == null || other.Type != Type)
{
return false;
}
return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default);
}
public override int GetEquivalenceHash()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Type);
foreach (var child in Children)
{
hashCodeCombiner.Add(child.GetEquivalenceHash());
}
return hashCodeCombiner.CombinedHash;
}
private class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
{
public static readonly EquivalenceComparer Default = new EquivalenceComparer();
private EquivalenceComparer()
{
}
public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY)
{
if (nodeX == nodeY)
{
return true;
}
return nodeX != null && nodeX.EquivalentTo(nodeY);
}
public int GetHashCode(SyntaxTreeNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
return node.GetEquivalenceHash();
}
}
}
}

View File

@ -0,0 +1,40 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class BlockBuilder
{
public BlockBuilder()
{
Reset();
}
public BlockBuilder(Block original)
{
Type = original.Type;
Children = new List<SyntaxTreeNode>(original.Children);
ChunkGenerator = original.ChunkGenerator;
}
public IParentChunkGenerator ChunkGenerator { get; set; }
public BlockType? Type { get; set; }
public List<SyntaxTreeNode> Children { get; private set; }
public virtual Block Build()
{
return new Block(this);
}
public virtual void Reset()
{
Type = null;
Children = new List<SyntaxTreeNode>();
ChunkGenerator = ParentChunkGenerator.Null;
}
}
}

View File

@ -0,0 +1,24 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal enum BlockType
{
// Code
Statement,
Directive,
Functions,
Expression,
Helper,
// Markup
Markup,
Section,
Template,
// Special
Comment,
Tag
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal enum CSharpKeyword
{
Await,
Abstract,
Byte,
Class,
Delegate,
Event,
Fixed,
If,
Internal,
New,
Override,
Readonly,
Short,
Struct,
Try,
Unsafe,
Volatile,
As,
Do,
Is,
Params,
Ref,
Switch,
Ushort,
While,
Case,
Const,
Explicit,
Float,
Null,
Sizeof,
Typeof,
Implicit,
Private,
This,
Using,
Extern,
Return,
Stackalloc,
Uint,
Base,
Catch,
Continue,
Double,
For,
In,
Lock,
Object,
Protected,
Static,
False,
Public,
Sbyte,
Throw,
Virtual,
Decimal,
Else,
Operator,
String,
Ulong,
Bool,
Char,
Default,
Foreach,
Long,
Void,
Enum,
Finally,
Int,
Out,
Sealed,
True,
Goto,
Unchecked,
Interface,
Break,
Checked,
Namespace,
When
}
}

View File

@ -0,0 +1,175 @@
// 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 System.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpLanguageCharacteristics : LanguageCharacteristics<CSharpTokenizer, CSharpSymbol, CSharpSymbolType>
{
private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
private static Dictionary<CSharpSymbolType, string> _symbolSamples = new Dictionary<CSharpSymbolType, string>()
{
{ CSharpSymbolType.Arrow, "->" },
{ CSharpSymbolType.Minus, "-" },
{ CSharpSymbolType.Decrement, "--" },
{ CSharpSymbolType.MinusAssign, "-=" },
{ CSharpSymbolType.NotEqual, "!=" },
{ CSharpSymbolType.Not, "!" },
{ CSharpSymbolType.Modulo, "%" },
{ CSharpSymbolType.ModuloAssign, "%=" },
{ CSharpSymbolType.AndAssign, "&=" },
{ CSharpSymbolType.And, "&" },
{ CSharpSymbolType.DoubleAnd, "&&" },
{ CSharpSymbolType.LeftParenthesis, "(" },
{ CSharpSymbolType.RightParenthesis, ")" },
{ CSharpSymbolType.Star, "*" },
{ CSharpSymbolType.MultiplyAssign, "*=" },
{ CSharpSymbolType.Comma, "," },
{ CSharpSymbolType.Dot, "." },
{ CSharpSymbolType.Slash, "/" },
{ CSharpSymbolType.DivideAssign, "/=" },
{ CSharpSymbolType.DoubleColon, "::" },
{ CSharpSymbolType.Colon, ":" },
{ CSharpSymbolType.Semicolon, ";" },
{ CSharpSymbolType.QuestionMark, "?" },
{ CSharpSymbolType.NullCoalesce, "??" },
{ CSharpSymbolType.RightBracket, "]" },
{ CSharpSymbolType.LeftBracket, "[" },
{ CSharpSymbolType.XorAssign, "^=" },
{ CSharpSymbolType.Xor, "^" },
{ CSharpSymbolType.LeftBrace, "{" },
{ CSharpSymbolType.OrAssign, "|=" },
{ CSharpSymbolType.DoubleOr, "||" },
{ CSharpSymbolType.Or, "|" },
{ CSharpSymbolType.RightBrace, "}" },
{ CSharpSymbolType.Tilde, "~" },
{ CSharpSymbolType.Plus, "+" },
{ CSharpSymbolType.PlusAssign, "+=" },
{ CSharpSymbolType.Increment, "++" },
{ CSharpSymbolType.LessThan, "<" },
{ CSharpSymbolType.LessThanEqual, "<=" },
{ CSharpSymbolType.LeftShift, "<<" },
{ CSharpSymbolType.LeftShiftAssign, "<<=" },
{ CSharpSymbolType.Assign, "=" },
{ CSharpSymbolType.Equals, "==" },
{ CSharpSymbolType.GreaterThan, ">" },
{ CSharpSymbolType.GreaterThanEqual, ">=" },
{ CSharpSymbolType.RightShift, ">>" },
{ CSharpSymbolType.RightShiftAssign, ">>>" },
{ CSharpSymbolType.Hash, "#" },
{ CSharpSymbolType.Transition, "@" },
};
private CSharpLanguageCharacteristics()
{
}
public static CSharpLanguageCharacteristics Instance => _instance;
public override CSharpTokenizer CreateTokenizer(ITextDocument source)
{
return new CSharpTokenizer(source);
}
protected override CSharpSymbol CreateSymbol(SourceLocation location, string content, CSharpSymbolType type, IReadOnlyList<RazorError> errors)
{
return new CSharpSymbol(location, content, type, errors);
}
public override string GetSample(CSharpSymbolType type)
{
string sample;
if (!_symbolSamples.TryGetValue(type, out sample))
{
switch (type)
{
case CSharpSymbolType.Identifier:
return LegacyResources.CSharpSymbol_Identifier;
case CSharpSymbolType.Keyword:
return LegacyResources.CSharpSymbol_Keyword;
case CSharpSymbolType.IntegerLiteral:
return LegacyResources.CSharpSymbol_IntegerLiteral;
case CSharpSymbolType.NewLine:
return LegacyResources.CSharpSymbol_Newline;
case CSharpSymbolType.WhiteSpace:
return LegacyResources.CSharpSymbol_Whitespace;
case CSharpSymbolType.Comment:
return LegacyResources.CSharpSymbol_Comment;
case CSharpSymbolType.RealLiteral:
return LegacyResources.CSharpSymbol_RealLiteral;
case CSharpSymbolType.CharacterLiteral:
return LegacyResources.CSharpSymbol_CharacterLiteral;
case CSharpSymbolType.StringLiteral:
return LegacyResources.CSharpSymbol_StringLiteral;
default:
return LegacyResources.Symbol_Unknown;
}
}
return sample;
}
public override CSharpSymbol CreateMarkerSymbol(SourceLocation location)
{
return new CSharpSymbol(location, string.Empty, CSharpSymbolType.Unknown);
}
public override CSharpSymbolType GetKnownSymbolType(KnownSymbolType type)
{
switch (type)
{
case KnownSymbolType.Identifier:
return CSharpSymbolType.Identifier;
case KnownSymbolType.Keyword:
return CSharpSymbolType.Keyword;
case KnownSymbolType.NewLine:
return CSharpSymbolType.NewLine;
case KnownSymbolType.WhiteSpace:
return CSharpSymbolType.WhiteSpace;
case KnownSymbolType.Transition:
return CSharpSymbolType.Transition;
case KnownSymbolType.CommentStart:
return CSharpSymbolType.RazorCommentTransition;
case KnownSymbolType.CommentStar:
return CSharpSymbolType.RazorCommentStar;
case KnownSymbolType.CommentBody:
return CSharpSymbolType.RazorComment;
default:
return CSharpSymbolType.Unknown;
}
}
public override CSharpSymbolType FlipBracket(CSharpSymbolType bracket)
{
switch (bracket)
{
case CSharpSymbolType.LeftBrace:
return CSharpSymbolType.RightBrace;
case CSharpSymbolType.LeftBracket:
return CSharpSymbolType.RightBracket;
case CSharpSymbolType.LeftParenthesis:
return CSharpSymbolType.RightParenthesis;
case CSharpSymbolType.LessThan:
return CSharpSymbolType.GreaterThan;
case CSharpSymbolType.RightBrace:
return CSharpSymbolType.LeftBrace;
case CSharpSymbolType.RightBracket:
return CSharpSymbolType.LeftBracket;
case CSharpSymbolType.RightParenthesis:
return CSharpSymbolType.LeftParenthesis;
case CSharpSymbolType.GreaterThan:
return CSharpSymbolType.LessThan;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
return CSharpSymbolType.Unknown;
}
}
public static string GetKeyword(CSharpKeyword keyword)
{
return keyword.ToString().ToLowerInvariant();
}
}
}

View File

@ -0,0 +1,72 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpSymbol : SymbolBase<CSharpSymbolType>
{
public CSharpSymbol(int absoluteIndex, int lineIndex, int characterIndex, string content, CSharpSymbolType type)
: this(new SourceLocation(absoluteIndex, lineIndex, characterIndex), content, type, RazorError.EmptyArray)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public CSharpSymbol(SourceLocation start, string content, CSharpSymbolType type)
: this(start, content, type, RazorError.EmptyArray)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public CSharpSymbol(
int offset,
int line,
int column,
string content,
CSharpSymbolType type,
IReadOnlyList<RazorError> errors)
: base(new SourceLocation(offset, line, column), content, type, errors)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public CSharpSymbol(
SourceLocation start,
string content,
CSharpSymbolType type,
IReadOnlyList<RazorError> errors)
: base(start, content, type, errors)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public CSharpKeyword? Keyword { get; set; }
public override bool Equals(object obj)
{
var other = obj as CSharpSymbol;
return base.Equals(other) &&
other.Keyword == Keyword;
}
public override int GetHashCode()
{
// Hash code should include only immutable properties.
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,75 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal enum CSharpSymbolType
{
Unknown,
Identifier,
Keyword,
IntegerLiteral,
NewLine,
WhiteSpace,
Comment,
RealLiteral,
CharacterLiteral,
StringLiteral,
// Operators
Arrow,
Minus,
Decrement,
MinusAssign,
NotEqual,
Not,
Modulo,
ModuloAssign,
AndAssign,
And,
DoubleAnd,
LeftParenthesis,
RightParenthesis,
Star,
MultiplyAssign,
Comma,
Dot,
Slash,
DivideAssign,
DoubleColon,
Colon,
Semicolon,
QuestionMark,
NullCoalesce,
RightBracket,
LeftBracket,
XorAssign,
Xor,
LeftBrace,
OrAssign,
DoubleOr,
Or,
RightBrace,
Tilde,
Plus,
PlusAssign,
Increment,
LessThan,
LessThanEqual,
LeftShift,
LeftShiftAssign,
Assign,
Equals,
GreaterThan,
GreaterThanEqual,
RightShift,
RightShiftAssign,
Hash,
Transition,
// Razor specific
RazorCommentTransition,
RazorCommentStar,
RazorComment
}
}

View File

@ -0,0 +1,625 @@
// 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.Globalization;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizer : Tokenizer<CSharpSymbol, CSharpSymbolType>
{
private Dictionary<char, Func<CSharpSymbolType>> _operatorHandlers;
private static readonly Dictionary<string, CSharpKeyword> _keywords = new Dictionary<string, CSharpKeyword>(StringComparer.Ordinal)
{
{ "await", CSharpKeyword.Await },
{ "abstract", CSharpKeyword.Abstract },
{ "byte", CSharpKeyword.Byte },
{ "class", CSharpKeyword.Class },
{ "delegate", CSharpKeyword.Delegate },
{ "event", CSharpKeyword.Event },
{ "fixed", CSharpKeyword.Fixed },
{ "if", CSharpKeyword.If },
{ "internal", CSharpKeyword.Internal },
{ "new", CSharpKeyword.New },
{ "override", CSharpKeyword.Override },
{ "readonly", CSharpKeyword.Readonly },
{ "short", CSharpKeyword.Short },
{ "struct", CSharpKeyword.Struct },
{ "try", CSharpKeyword.Try },
{ "unsafe", CSharpKeyword.Unsafe },
{ "volatile", CSharpKeyword.Volatile },
{ "as", CSharpKeyword.As },
{ "do", CSharpKeyword.Do },
{ "is", CSharpKeyword.Is },
{ "params", CSharpKeyword.Params },
{ "ref", CSharpKeyword.Ref },
{ "switch", CSharpKeyword.Switch },
{ "ushort", CSharpKeyword.Ushort },
{ "while", CSharpKeyword.While },
{ "case", CSharpKeyword.Case },
{ "const", CSharpKeyword.Const },
{ "explicit", CSharpKeyword.Explicit },
{ "float", CSharpKeyword.Float },
{ "null", CSharpKeyword.Null },
{ "sizeof", CSharpKeyword.Sizeof },
{ "typeof", CSharpKeyword.Typeof },
{ "implicit", CSharpKeyword.Implicit },
{ "private", CSharpKeyword.Private },
{ "this", CSharpKeyword.This },
{ "using", CSharpKeyword.Using },
{ "extern", CSharpKeyword.Extern },
{ "return", CSharpKeyword.Return },
{ "stackalloc", CSharpKeyword.Stackalloc },
{ "uint", CSharpKeyword.Uint },
{ "base", CSharpKeyword.Base },
{ "catch", CSharpKeyword.Catch },
{ "continue", CSharpKeyword.Continue },
{ "double", CSharpKeyword.Double },
{ "for", CSharpKeyword.For },
{ "in", CSharpKeyword.In },
{ "lock", CSharpKeyword.Lock },
{ "object", CSharpKeyword.Object },
{ "protected", CSharpKeyword.Protected },
{ "static", CSharpKeyword.Static },
{ "false", CSharpKeyword.False },
{ "public", CSharpKeyword.Public },
{ "sbyte", CSharpKeyword.Sbyte },
{ "throw", CSharpKeyword.Throw },
{ "virtual", CSharpKeyword.Virtual },
{ "decimal", CSharpKeyword.Decimal },
{ "else", CSharpKeyword.Else },
{ "operator", CSharpKeyword.Operator },
{ "string", CSharpKeyword.String },
{ "ulong", CSharpKeyword.Ulong },
{ "bool", CSharpKeyword.Bool },
{ "char", CSharpKeyword.Char },
{ "default", CSharpKeyword.Default },
{ "foreach", CSharpKeyword.Foreach },
{ "long", CSharpKeyword.Long },
{ "void", CSharpKeyword.Void },
{ "enum", CSharpKeyword.Enum },
{ "finally", CSharpKeyword.Finally },
{ "int", CSharpKeyword.Int },
{ "out", CSharpKeyword.Out },
{ "sealed", CSharpKeyword.Sealed },
{ "true", CSharpKeyword.True },
{ "goto", CSharpKeyword.Goto },
{ "unchecked", CSharpKeyword.Unchecked },
{ "interface", CSharpKeyword.Interface },
{ "break", CSharpKeyword.Break },
{ "checked", CSharpKeyword.Checked },
{ "namespace", CSharpKeyword.Namespace },
{ "when", CSharpKeyword.When }
};
public CSharpTokenizer(ITextDocument source)
: base(source)
{
base.CurrentState = StartState;
_operatorHandlers = new Dictionary<char, Func<CSharpSymbolType>>()
{
{ '-', MinusOperator },
{ '<', LessThanOperator },
{ '>', GreaterThanOperator },
{ '&', CreateTwoCharOperatorHandler(CSharpSymbolType.And, '=', CSharpSymbolType.AndAssign, '&', CSharpSymbolType.DoubleAnd) },
{ '|', CreateTwoCharOperatorHandler(CSharpSymbolType.Or, '=', CSharpSymbolType.OrAssign, '|', CSharpSymbolType.DoubleOr) },
{ '+', CreateTwoCharOperatorHandler(CSharpSymbolType.Plus, '=', CSharpSymbolType.PlusAssign, '+', CSharpSymbolType.Increment) },
{ '=', CreateTwoCharOperatorHandler(CSharpSymbolType.Assign, '=', CSharpSymbolType.Equals, '>', CSharpSymbolType.GreaterThanEqual) },
{ '!', CreateTwoCharOperatorHandler(CSharpSymbolType.Not, '=', CSharpSymbolType.NotEqual) },
{ '%', CreateTwoCharOperatorHandler(CSharpSymbolType.Modulo, '=', CSharpSymbolType.ModuloAssign) },
{ '*', CreateTwoCharOperatorHandler(CSharpSymbolType.Star, '=', CSharpSymbolType.MultiplyAssign) },
{ ':', CreateTwoCharOperatorHandler(CSharpSymbolType.Colon, ':', CSharpSymbolType.DoubleColon) },
{ '?', CreateTwoCharOperatorHandler(CSharpSymbolType.QuestionMark, '?', CSharpSymbolType.NullCoalesce) },
{ '^', CreateTwoCharOperatorHandler(CSharpSymbolType.Xor, '=', CSharpSymbolType.XorAssign) },
{ '(', () => CSharpSymbolType.LeftParenthesis },
{ ')', () => CSharpSymbolType.RightParenthesis },
{ '{', () => CSharpSymbolType.LeftBrace },
{ '}', () => CSharpSymbolType.RightBrace },
{ '[', () => CSharpSymbolType.LeftBracket },
{ ']', () => CSharpSymbolType.RightBracket },
{ ',', () => CSharpSymbolType.Comma },
{ ';', () => CSharpSymbolType.Semicolon },
{ '~', () => CSharpSymbolType.Tilde },
{ '#', () => CSharpSymbolType.Hash }
};
}
protected override int StartState => (int)CSharpTokenizerState.Data;
private new CSharpTokenizerState? CurrentState => (CSharpTokenizerState?)base.CurrentState;
public override CSharpSymbolType RazorCommentType => CSharpSymbolType.RazorComment;
public override CSharpSymbolType RazorCommentTransitionType => CSharpSymbolType.RazorCommentTransition;
public override CSharpSymbolType RazorCommentStarType => CSharpSymbolType.RazorCommentStar;
protected override StateResult Dispatch()
{
switch (CurrentState)
{
case CSharpTokenizerState.Data:
return Data();
case CSharpTokenizerState.BlockComment:
return BlockComment();
case CSharpTokenizerState.QuotedCharacterLiteral:
return QuotedCharacterLiteral();
case CSharpTokenizerState.QuotedStringLiteral:
return QuotedStringLiteral();
case CSharpTokenizerState.VerbatimStringLiteral:
return VerbatimStringLiteral();
case CSharpTokenizerState.AfterRazorCommentTransition:
return AfterRazorCommentTransition();
case CSharpTokenizerState.EscapedRazorCommentTransition:
return EscapedRazorCommentTransition();
case CSharpTokenizerState.RazorCommentBody:
return RazorCommentBody();
case CSharpTokenizerState.StarAfterRazorCommentBody:
return StarAfterRazorCommentBody();
case CSharpTokenizerState.AtSymbolAfterRazorCommentBody:
return AtSymbolAfterRazorCommentBody();
default:
Debug.Fail("Invalid TokenizerState");
return default(StateResult);
}
}
protected override CSharpSymbol CreateSymbol(SourceLocation start, string content, CSharpSymbolType type, IReadOnlyList<RazorError> errors)
{
return new CSharpSymbol(start, content, type, errors);
}
private StateResult Data()
{
if (ParserHelpers.IsNewLine(CurrentCharacter))
{
// CSharp Spec §2.3.1
var checkTwoCharNewline = CurrentCharacter == '\r';
TakeCurrent();
if (checkTwoCharNewline && CurrentCharacter == '\n')
{
TakeCurrent();
}
return Stay(EndSymbol(CSharpSymbolType.NewLine));
}
else if (ParserHelpers.IsWhitespace(CurrentCharacter))
{
// CSharp Spec §2.3.3
TakeUntil(c => !ParserHelpers.IsWhitespace(c));
return Stay(EndSymbol(CSharpSymbolType.WhiteSpace));
}
else if (IsIdentifierStart(CurrentCharacter))
{
return Identifier();
}
else if (char.IsDigit(CurrentCharacter))
{
return NumericLiteral();
}
switch (CurrentCharacter)
{
case '@':
return AtSymbol();
case '\'':
TakeCurrent();
return Transition(CSharpTokenizerState.QuotedCharacterLiteral);
case '"':
TakeCurrent();
return Transition(CSharpTokenizerState.QuotedStringLiteral);
case '.':
if (char.IsDigit(Peek()))
{
return RealLiteral();
}
return Stay(Single(CSharpSymbolType.Dot));
case '/':
TakeCurrent();
if (CurrentCharacter == '/')
{
TakeCurrent();
return SingleLineComment();
}
else if (CurrentCharacter == '*')
{
TakeCurrent();
return Transition(CSharpTokenizerState.BlockComment);
}
else if (CurrentCharacter == '=')
{
TakeCurrent();
return Stay(EndSymbol(CSharpSymbolType.DivideAssign));
}
else
{
return Stay(EndSymbol(CSharpSymbolType.Slash));
}
default:
return Stay(EndSymbol(Operator()));
}
}
private StateResult AtSymbol()
{
TakeCurrent();
if (CurrentCharacter == '"')
{
TakeCurrent();
return Transition(CSharpTokenizerState.VerbatimStringLiteral);
}
else if (CurrentCharacter == '*')
{
return Transition(
CSharpTokenizerState.AfterRazorCommentTransition,
EndSymbol(CSharpSymbolType.RazorCommentTransition));
}
else if (CurrentCharacter == '@')
{
// Could be escaped comment transition
return Transition(
CSharpTokenizerState.EscapedRazorCommentTransition,
EndSymbol(CSharpSymbolType.Transition));
}
return Stay(EndSymbol(CSharpSymbolType.Transition));
}
private StateResult EscapedRazorCommentTransition()
{
TakeCurrent();
return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Transition));
}
private CSharpSymbolType Operator()
{
var first = CurrentCharacter;
TakeCurrent();
Func<CSharpSymbolType> handler;
if (_operatorHandlers.TryGetValue(first, out handler))
{
return handler();
}
return CSharpSymbolType.Unknown;
}
private CSharpSymbolType LessThanOperator()
{
if (CurrentCharacter == '=')
{
TakeCurrent();
return CSharpSymbolType.LessThanEqual;
}
return CSharpSymbolType.LessThan;
}
private CSharpSymbolType GreaterThanOperator()
{
if (CurrentCharacter == '=')
{
TakeCurrent();
return CSharpSymbolType.GreaterThanEqual;
}
return CSharpSymbolType.GreaterThan;
}
private CSharpSymbolType MinusOperator()
{
if (CurrentCharacter == '>')
{
TakeCurrent();
return CSharpSymbolType.Arrow;
}
else if (CurrentCharacter == '-')
{
TakeCurrent();
return CSharpSymbolType.Decrement;
}
else if (CurrentCharacter == '=')
{
TakeCurrent();
return CSharpSymbolType.MinusAssign;
}
return CSharpSymbolType.Minus;
}
private Func<CSharpSymbolType> CreateTwoCharOperatorHandler(CSharpSymbolType typeIfOnlyFirst, char second, CSharpSymbolType typeIfBoth)
{
return () =>
{
if (CurrentCharacter == second)
{
TakeCurrent();
return typeIfBoth;
}
return typeIfOnlyFirst;
};
}
private Func<CSharpSymbolType> CreateTwoCharOperatorHandler(CSharpSymbolType typeIfOnlyFirst, char option1, CSharpSymbolType typeIfOption1, char option2, CSharpSymbolType typeIfOption2)
{
return () =>
{
if (CurrentCharacter == option1)
{
TakeCurrent();
return typeIfOption1;
}
else if (CurrentCharacter == option2)
{
TakeCurrent();
return typeIfOption2;
}
return typeIfOnlyFirst;
};
}
private StateResult VerbatimStringLiteral()
{
TakeUntil(c => c == '"');
if (CurrentCharacter == '"')
{
TakeCurrent();
if (CurrentCharacter == '"')
{
TakeCurrent();
// Stay in the literal, this is an escaped "
return Stay();
}
}
else if (EndOfFile)
{
CurrentErrors.Add(
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
CurrentStart,
length: 1 /* end of file */));
}
return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.StringLiteral));
}
private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', CSharpSymbolType.CharacterLiteral);
private StateResult QuotedStringLiteral() => QuotedLiteral('\"', CSharpSymbolType.StringLiteral);
private StateResult QuotedLiteral(char quote, CSharpSymbolType literalType)
{
TakeUntil(c => c == '\\' || c == quote || ParserHelpers.IsNewLine(c));
if (CurrentCharacter == '\\')
{
TakeCurrent(); // Take the '\'
// If the next char is the same quote that started this
if (CurrentCharacter == quote || CurrentCharacter == '\\')
{
TakeCurrent(); // Take it so that we don't prematurely end the literal.
}
return Stay();
}
else if (EndOfFile || ParserHelpers.IsNewLine(CurrentCharacter))
{
CurrentErrors.Add(
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
CurrentStart,
length: 1 /* " */));
}
else
{
TakeCurrent(); // No-op if at EOF
}
return Transition(CSharpTokenizerState.Data, EndSymbol(literalType));
}
// CSharp Spec §2.3.2
private StateResult BlockComment()
{
TakeUntil(c => c == '*');
if (EndOfFile)
{
CurrentErrors.Add(
new RazorError(
LegacyResources.ParseError_BlockComment_Not_Terminated,
CurrentStart,
length: 1 /* end of file */));
return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Comment));
}
if (CurrentCharacter == '*')
{
TakeCurrent();
if (CurrentCharacter == '/')
{
TakeCurrent();
return Transition(CSharpTokenizerState.Data, EndSymbol(CSharpSymbolType.Comment));
}
}
return Stay();
}
// CSharp Spec §2.3.2
private StateResult SingleLineComment()
{
TakeUntil(c => ParserHelpers.IsNewLine(c));
return Stay(EndSymbol(CSharpSymbolType.Comment));
}
// CSharp Spec §2.4.4
private StateResult NumericLiteral()
{
if (TakeAll("0x", caseSensitive: true))
{
return HexLiteral();
}
else
{
return DecimalLiteral();
}
}
private StateResult HexLiteral()
{
TakeUntil(c => !IsHexDigit(c));
TakeIntegerSuffix();
return Stay(EndSymbol(CSharpSymbolType.IntegerLiteral));
}
private StateResult DecimalLiteral()
{
TakeUntil(c => !Char.IsDigit(c));
if (CurrentCharacter == '.' && Char.IsDigit(Peek()))
{
return RealLiteral();
}
else if (IsRealLiteralSuffix(CurrentCharacter) ||
CurrentCharacter == 'E' || CurrentCharacter == 'e')
{
return RealLiteralExponentPart();
}
else
{
TakeIntegerSuffix();
return Stay(EndSymbol(CSharpSymbolType.IntegerLiteral));
}
}
private StateResult RealLiteralExponentPart()
{
if (CurrentCharacter == 'E' || CurrentCharacter == 'e')
{
TakeCurrent();
if (CurrentCharacter == '+' || CurrentCharacter == '-')
{
TakeCurrent();
}
TakeUntil(c => !Char.IsDigit(c));
}
if (IsRealLiteralSuffix(CurrentCharacter))
{
TakeCurrent();
}
return Stay(EndSymbol(CSharpSymbolType.RealLiteral));
}
// CSharp Spec §2.4.4.3
private StateResult RealLiteral()
{
AssertCurrent('.');
TakeCurrent();
Debug.Assert(Char.IsDigit(CurrentCharacter));
TakeUntil(c => !Char.IsDigit(c));
return RealLiteralExponentPart();
}
private void TakeIntegerSuffix()
{
if (Char.ToLowerInvariant(CurrentCharacter) == 'u')
{
TakeCurrent();
if (Char.ToLowerInvariant(CurrentCharacter) == 'l')
{
TakeCurrent();
}
}
else if (Char.ToLowerInvariant(CurrentCharacter) == 'l')
{
TakeCurrent();
if (Char.ToLowerInvariant(CurrentCharacter) == 'u')
{
TakeCurrent();
}
}
}
// CSharp Spec §2.4.2
private StateResult Identifier()
{
Debug.Assert(IsIdentifierStart(CurrentCharacter));
TakeCurrent();
TakeUntil(c => !IsIdentifierPart(c));
CSharpSymbol symbol = null;
if (HaveContent)
{
CSharpKeyword keyword;
var type = CSharpSymbolType.Identifier;
if (_keywords.TryGetValue(Buffer.ToString(), out keyword))
{
type = CSharpSymbolType.Keyword;
}
symbol = new CSharpSymbol(CurrentStart, Buffer.ToString(), type)
{
Keyword = type == CSharpSymbolType.Keyword ? (CSharpKeyword?)keyword : null,
};
}
StartSymbol();
return Stay(symbol);
}
private StateResult Transition(CSharpTokenizerState state)
{
return Transition((int)state, result: null);
}
private StateResult Transition(CSharpTokenizerState state, CSharpSymbol result)
{
return Transition((int)state, result);
}
private static bool IsIdentifierStart(char character)
{
return char.IsLetter(character) ||
character == '_' ||
CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.LetterNumber;
}
private static bool IsIdentifierPart(char character)
{
return char.IsDigit(character) ||
IsIdentifierStart(character) ||
IsIdentifierPartByUnicodeCategory(character);
}
private static bool IsRealLiteralSuffix(char character)
{
return character == 'F' ||
character == 'f' ||
character == 'D' ||
character == 'd' ||
character == 'M' ||
character == 'm';
}
private static bool IsIdentifierPartByUnicodeCategory(char character)
{
var category = CharUnicodeInfo.GetUnicodeCategory(character);
return category == UnicodeCategory.NonSpacingMark || // Mn
category == UnicodeCategory.SpacingCombiningMark || // Mc
category == UnicodeCategory.ConnectorPunctuation || // Pc
category == UnicodeCategory.Format; // Cf
}
private static bool IsHexDigit(char value)
{
return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
}
private enum CSharpTokenizerState
{
Data,
BlockComment,
QuotedCharacterLiteral,
QuotedStringLiteral,
VerbatimStringLiteral,
// Razor Comments - need to be the same for HTML and CSharp
AfterRazorCommentTransition = RazorCommentTokenizerState.AfterRazorCommentTransition,
EscapedRazorCommentTransition = RazorCommentTokenizerState.EscapedRazorCommentTransition,
RazorCommentBody = RazorCommentTokenizerState.RazorCommentBody,
StarAfterRazorCommentBody = RazorCommentTokenizerState.StarAfterRazorCommentBody,
AtSymbolAfterRazorCommentBody = RazorCommentTokenizerState.AtSymbolAfterRazorCommentBody,
}
}
}

View File

@ -0,0 +1,25 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class ChunkGeneratorContext
{
public ChunkGeneratorContext(
string className,
string rootNamespace,
string sourceFile,
bool shouldGenerateLinePragmas)
{
SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
RootNamespace = rootNamespace;
ClassName = className;
}
public string SourceFile { get; internal set; }
public string RootNamespace { get; }
public string ClassName { get; }
}
}

View File

@ -0,0 +1,32 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class DisposableAction : IDisposable
{
private readonly Action _action;
private bool _invoked;
public DisposableAction(Action action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
_action = action;
}
public void Dispose()
{
if (!_invoked)
{
_action();
_invoked = true;
}
}
}
}

View File

@ -0,0 +1,54 @@
// 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.Globalization;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
{
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, int offset, int line, int col)
: this(prefix, new SourceLocation(offset, line, col))
{
}
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, SourceLocation valueStart)
{
Prefix = prefix;
ValueStart = valueStart;
}
public LocationTagged<string> Prefix { get; }
public SourceLocation ValueStart { get; }
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//var chunk = context.ChunkTreeBuilder.StartParentChunk<DynamicCodeAttributeChunk>(target);
//chunk.Start = ValueStart;
//chunk.Prefix = Prefix;
}
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.EndParentChunk();
}
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
}
public override bool Equals(object obj)
{
var other = obj as DynamicAttributeBlockChunkGenerator;
return other != null &&
Equals(other.Prefix, Prefix);
}
public override int GetHashCode()
{
return Prefix == null ? 0 : Prefix.GetHashCode();
}
}
}

View File

@ -0,0 +1,17 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class EditResult
{
public EditResult(PartialParseResult result, SpanBuilder editedSpan)
{
Result = result;
EditedSpan = editedSpan;
}
public PartialParseResult Result { get; set; }
public SpanBuilder EditedSpan { get; set; }
}
}

View File

@ -0,0 +1,46 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
/// <summary>
/// Used to manage <see cref="RazorError"/>s encountered during the Razor parsing phase.
/// </summary>
internal class ErrorSink
{
private readonly List<RazorError> _errors;
/// <summary>
/// Instantiates a new instance of <see cref="ErrorSink"/>.
/// </summary>
public ErrorSink()
{
_errors = new List<RazorError>();
}
/// <summary>
/// <see cref="RazorError"/>s collected.
/// </summary>
public IEnumerable<RazorError> Errors => _errors;
/// <summary>
/// Tracks the given <paramref name="error"/>.
/// </summary>
/// <param name="error">The <see cref="RazorError"/> to track.</param>
public void OnError(RazorError error) =>_errors.Add(error);
/// <summary>
/// Creates and tracks a new <see cref="RazorError"/>.
/// </summary>
/// <param name="location"><see cref="SourceLocation"/> of the error.</param>
/// <param name="message">A message describing the error.</param>
/// <param name="length">The length of the error.</param>
public void OnError(SourceLocation location, string message, int length)
{
var error = new RazorError(message, location, length);
_errors.Add(error);
}
}
}

View File

@ -0,0 +1,41 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
{
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.StartParentChunk<ExpressionBlockChunk>(target);
}
public void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
}
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.EndParentChunk();
}
public override string ToString()
{
return "Expr";
}
public override bool Equals(object obj)
{
return obj != null &&
GetType() == obj.GetType();
}
public override int GetHashCode()
{
return TypeHashCode;
}
}
}

View File

@ -0,0 +1,133 @@
// 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 System.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class HtmlLanguageCharacteristics : LanguageCharacteristics<HtmlTokenizer, HtmlSymbol, HtmlSymbolType>
{
private static readonly HtmlLanguageCharacteristics _instance = new HtmlLanguageCharacteristics();
private HtmlLanguageCharacteristics()
{
}
public static HtmlLanguageCharacteristics Instance
{
get { return _instance; }
}
public override string GetSample(HtmlSymbolType type)
{
switch (type)
{
case HtmlSymbolType.Text:
return LegacyResources.HtmlSymbol_Text;
case HtmlSymbolType.WhiteSpace:
return LegacyResources.HtmlSymbol_WhiteSpace;
case HtmlSymbolType.NewLine:
return LegacyResources.HtmlSymbol_NewLine;
case HtmlSymbolType.OpenAngle:
return "<";
case HtmlSymbolType.Bang:
return "!";
case HtmlSymbolType.ForwardSlash:
return "/";
case HtmlSymbolType.QuestionMark:
return "?";
case HtmlSymbolType.DoubleHyphen:
return "--";
case HtmlSymbolType.LeftBracket:
return "[";
case HtmlSymbolType.CloseAngle:
return ">";
case HtmlSymbolType.RightBracket:
return "]";
case HtmlSymbolType.Equals:
return "=";
case HtmlSymbolType.DoubleQuote:
return "\"";
case HtmlSymbolType.SingleQuote:
return "'";
case HtmlSymbolType.Transition:
return "@";
case HtmlSymbolType.Colon:
return ":";
case HtmlSymbolType.RazorComment:
return LegacyResources.HtmlSymbol_RazorComment;
case HtmlSymbolType.RazorCommentStar:
return "*";
case HtmlSymbolType.RazorCommentTransition:
return "@";
default:
return LegacyResources.Symbol_Unknown;
}
}
public override HtmlTokenizer CreateTokenizer(ITextDocument source)
{
return new HtmlTokenizer(source);
}
public override HtmlSymbolType FlipBracket(HtmlSymbolType bracket)
{
switch (bracket)
{
case HtmlSymbolType.LeftBracket:
return HtmlSymbolType.RightBracket;
case HtmlSymbolType.OpenAngle:
return HtmlSymbolType.CloseAngle;
case HtmlSymbolType.RightBracket:
return HtmlSymbolType.LeftBracket;
case HtmlSymbolType.CloseAngle:
return HtmlSymbolType.OpenAngle;
default:
#if NET451
// No Debug.Fail in CoreCLR
Debug.Fail("FlipBracket must be called with a bracket character");
#else
Debug.Assert(false, "FlipBracket must be called with a bracket character");
#endif
return HtmlSymbolType.Unknown;
}
}
public override HtmlSymbol CreateMarkerSymbol(SourceLocation location)
{
return new HtmlSymbol(location, string.Empty, HtmlSymbolType.Unknown);
}
public override HtmlSymbolType GetKnownSymbolType(KnownSymbolType type)
{
switch (type)
{
case KnownSymbolType.CommentStart:
return HtmlSymbolType.RazorCommentTransition;
case KnownSymbolType.CommentStar:
return HtmlSymbolType.RazorCommentStar;
case KnownSymbolType.CommentBody:
return HtmlSymbolType.RazorComment;
case KnownSymbolType.Identifier:
return HtmlSymbolType.Text;
case KnownSymbolType.Keyword:
return HtmlSymbolType.Text;
case KnownSymbolType.NewLine:
return HtmlSymbolType.NewLine;
case KnownSymbolType.Transition:
return HtmlSymbolType.Transition;
case KnownSymbolType.WhiteSpace:
return HtmlSymbolType.WhiteSpace;
default:
return HtmlSymbolType.Unknown;
}
}
protected override HtmlSymbol CreateSymbol(SourceLocation location, string content, HtmlSymbolType type, IReadOnlyList<RazorError> errors)
{
return new HtmlSymbol(location, content, type, errors);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class HtmlSymbol : SymbolBase<HtmlSymbolType>
{
public HtmlSymbol(int absoluteIndex, int lineIndex, int characterIndex, string content, HtmlSymbolType type)
: this(new SourceLocation(absoluteIndex, lineIndex, characterIndex), content, type, RazorError.EmptyArray)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public HtmlSymbol(SourceLocation start, string content, HtmlSymbolType type)
: base(start, content, type, RazorError.EmptyArray)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public HtmlSymbol(
int absoluteIndex,
int lineIndex,
int characterIndex,
string content,
HtmlSymbolType type,
IReadOnlyList<RazorError> errors)
: base(new SourceLocation(absoluteIndex, lineIndex, characterIndex), content, type, errors)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
public HtmlSymbol(
SourceLocation start,
string content,
HtmlSymbolType type,
IReadOnlyList<RazorError> errors)
: base(start, content, type, errors)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
}
}
}

View File

@ -0,0 +1,32 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
[Flags]
internal enum HtmlSymbolType
{
Unknown,
Text, // Text which isn't one of the below
WhiteSpace, // Non-newline Whitespace
NewLine, // Newline
OpenAngle, // <
Bang, // !
ForwardSlash, // /
QuestionMark, // ?
DoubleHyphen, // --
LeftBracket, // [
CloseAngle, // >
RightBracket, // ]
Equals, // =
DoubleQuote, // "
SingleQuote, // '
Transition, // @
Colon,
RazorComment,
RazorCommentStar,
RazorCommentTransition
}
}

View File

@ -0,0 +1,250 @@
// 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 System.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// Tokenizer _loosely_ based on http://dev.w3.org/html5/spec/Overview.html#tokenization
internal class HtmlTokenizer : Tokenizer<HtmlSymbol, HtmlSymbolType>
{
private const char TransitionChar = '@';
public HtmlTokenizer(ITextDocument source)
: base(source)
{
base.CurrentState = StartState;
}
protected override int StartState => (int)HtmlTokenizerState.Data;
private new HtmlTokenizerState? CurrentState => (HtmlTokenizerState?)base.CurrentState;
public override HtmlSymbolType RazorCommentType
{
get { return HtmlSymbolType.RazorComment; }
}
public override HtmlSymbolType RazorCommentTransitionType
{
get { return HtmlSymbolType.RazorCommentTransition; }
}
public override HtmlSymbolType RazorCommentStarType
{
get { return HtmlSymbolType.RazorCommentStar; }
}
protected override HtmlSymbol CreateSymbol(SourceLocation start, string content, HtmlSymbolType type, IReadOnlyList<RazorError> errors)
{
return new HtmlSymbol(start, content, type, errors);
}
protected override StateResult Dispatch()
{
switch (CurrentState)
{
case HtmlTokenizerState.Data:
return Data();
case HtmlTokenizerState.Text:
return Text();
case HtmlTokenizerState.AfterRazorCommentTransition:
return AfterRazorCommentTransition();
case HtmlTokenizerState.EscapedRazorCommentTransition:
return EscapedRazorCommentTransition();
case HtmlTokenizerState.RazorCommentBody:
return RazorCommentBody();
case HtmlTokenizerState.StarAfterRazorCommentBody:
return StarAfterRazorCommentBody();
case HtmlTokenizerState.AtSymbolAfterRazorCommentBody:
return AtSymbolAfterRazorCommentBody();
default:
#if NET451
// No Debug.Fail
Debug.Fail("Invalid TokenizerState");
#else
Debug.Assert(false, "Invalid TokenizerState");
#endif
return default(StateResult);
}
}
// http://dev.w3.org/html5/spec/Overview.html#data-state
private StateResult Data()
{
if (ParserHelpers.IsWhitespace(CurrentCharacter))
{
return Stay(Whitespace());
}
else if (ParserHelpers.IsNewLine(CurrentCharacter))
{
return Stay(Newline());
}
else if (CurrentCharacter == '@')
{
TakeCurrent();
if (CurrentCharacter == '*')
{
return Transition(
HtmlTokenizerState.AfterRazorCommentTransition,
EndSymbol(HtmlSymbolType.RazorCommentTransition));
}
else if (CurrentCharacter == '@')
{
// Could be escaped comment transition
return Transition(
HtmlTokenizerState.EscapedRazorCommentTransition,
EndSymbol(HtmlSymbolType.Transition));
}
return Stay(EndSymbol(HtmlSymbolType.Transition));
}
else if (AtSymbol())
{
return Stay(Symbol());
}
else
{
return Transition(HtmlTokenizerState.Text);
}
}
private StateResult EscapedRazorCommentTransition()
{
TakeCurrent();
return Transition(HtmlTokenizerState.Data, EndSymbol(HtmlSymbolType.Transition));
}
private StateResult Text()
{
var prev = '\0';
while (!EndOfFile &&
!(ParserHelpers.IsWhitespace(CurrentCharacter) || ParserHelpers.IsNewLine(CurrentCharacter)) &&
!AtSymbol())
{
prev = CurrentCharacter;
TakeCurrent();
}
if (CurrentCharacter == '@')
{
var next = Peek();
if ((ParserHelpers.IsLetter(prev) || ParserHelpers.IsDecimalDigit(prev)) &&
(ParserHelpers.IsLetter(next) || ParserHelpers.IsDecimalDigit(next)))
{
TakeCurrent(); // Take the "@"
return Stay(); // Stay in the Text state
}
}
// Output the Text token and return to the Data state to tokenize the next character (if there is one)
return Transition(HtmlTokenizerState.Data, EndSymbol(HtmlSymbolType.Text));
}
private HtmlSymbol Symbol()
{
Debug.Assert(AtSymbol());
var sym = CurrentCharacter;
TakeCurrent();
switch (sym)
{
case '<':
return EndSymbol(HtmlSymbolType.OpenAngle);
case '!':
return EndSymbol(HtmlSymbolType.Bang);
case '/':
return EndSymbol(HtmlSymbolType.ForwardSlash);
case '?':
return EndSymbol(HtmlSymbolType.QuestionMark);
case '[':
return EndSymbol(HtmlSymbolType.LeftBracket);
case '>':
return EndSymbol(HtmlSymbolType.CloseAngle);
case ']':
return EndSymbol(HtmlSymbolType.RightBracket);
case '=':
return EndSymbol(HtmlSymbolType.Equals);
case '"':
return EndSymbol(HtmlSymbolType.DoubleQuote);
case '\'':
return EndSymbol(HtmlSymbolType.SingleQuote);
case '-':
Debug.Assert(CurrentCharacter == '-');
TakeCurrent();
return EndSymbol(HtmlSymbolType.DoubleHyphen);
default:
#if NET451
// No Debug.Fail in CoreCLR
Debug.Fail("Unexpected symbol!");
#else
Debug.Assert(false, "Unexpected symbol");
#endif
return EndSymbol(HtmlSymbolType.Unknown);
}
}
private HtmlSymbol Whitespace()
{
while (ParserHelpers.IsWhitespace(CurrentCharacter))
{
TakeCurrent();
}
return EndSymbol(HtmlSymbolType.WhiteSpace);
}
private HtmlSymbol Newline()
{
Debug.Assert(ParserHelpers.IsNewLine(CurrentCharacter));
// CSharp Spec §2.3.1
var checkTwoCharNewline = CurrentCharacter == '\r';
TakeCurrent();
if (checkTwoCharNewline && CurrentCharacter == '\n')
{
TakeCurrent();
}
return EndSymbol(HtmlSymbolType.NewLine);
}
private bool AtSymbol()
{
return CurrentCharacter == '<' ||
CurrentCharacter == '<' ||
CurrentCharacter == '!' ||
CurrentCharacter == '/' ||
CurrentCharacter == '?' ||
CurrentCharacter == '[' ||
CurrentCharacter == '>' ||
CurrentCharacter == ']' ||
CurrentCharacter == '=' ||
CurrentCharacter == '"' ||
CurrentCharacter == '\'' ||
CurrentCharacter == '@' ||
(CurrentCharacter == '-' && Peek() == '-');
}
private StateResult Transition(HtmlTokenizerState state)
{
return Transition((int)state, result: null);
}
private StateResult Transition(HtmlTokenizerState state, HtmlSymbol result)
{
return Transition((int)state, result);
}
private enum HtmlTokenizerState
{
Data,
Text,
// Razor Comments - need to be the same for HTML and CSharp
AfterRazorCommentTransition = RazorCommentTokenizerState.AfterRazorCommentTransition,
EscapedRazorCommentTransition = RazorCommentTokenizerState.EscapedRazorCommentTransition,
RazorCommentBody = RazorCommentTokenizerState.RazorCommentBody,
StarAfterRazorCommentBody = RazorCommentTokenizerState.StarAfterRazorCommentBody,
AtSymbolAfterRazorCommentBody = RazorCommentTokenizerState.AtSymbolAfterRazorCommentBody,
}
}
}

View File

@ -0,0 +1,11 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface IParentChunkGenerator
{
void GenerateStartParentChunk(Block target, ChunkGeneratorContext context);
void GenerateEndParentChunk(Block target, ChunkGeneratorContext context);
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface ISpanChunkGenerator
{
void GenerateChunk(Span target, ChunkGeneratorContext context);
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface ISymbol
{
SourceLocation Start { get; }
string Content { get; }
void OffsetStart(SourceLocation documentStart);
void ChangeStart(SourceLocation newStart);
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface ITextBuffer
{
int Length { get; }
int Position { get; set; }
int Read();
int Peek();
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface ITextDocument : ITextBuffer
{
SourceLocation Location { get; }
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal interface ITokenizer
{
ISymbol NextSymbol();
}
}

View File

@ -0,0 +1,320 @@
// 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.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class ImplicitExpressionEditHandler : SpanEditHandler
{
private readonly ISet<string> _keywords;
private readonly IReadOnlyCollection<string> _readOnlyKeywords;
public ImplicitExpressionEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, ISet<string> keywords, bool acceptTrailingDot)
: base(tokenizer)
{
_keywords = keywords ?? new HashSet<string>();
// HashSet<T> implements IReadOnlyCollection<T> as of 4.6, but does not for 4.5.1. If the runtime cast
// succeeds, avoid creating a new collection.
_readOnlyKeywords = (_keywords as IReadOnlyCollection<string>) ?? _keywords.ToArray();
AcceptTrailingDot = acceptTrailingDot;
}
public bool AcceptTrailingDot { get; }
public IReadOnlyCollection<string> Keywords
{
get
{
return _readOnlyKeywords;
}
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0};ImplicitExpression[{1}];K{2}", base.ToString(), AcceptTrailingDot ? "ATD" : "RTD", Keywords.Count);
}
public override bool Equals(object obj)
{
var other = obj as ImplicitExpressionEditHandler;
return base.Equals(other) &&
_keywords.SetEquals(other._keywords) &&
AcceptTrailingDot == other.AcceptTrailingDot;
}
public override int GetHashCode()
{
// Hash code should include only immutable properties and base has none.
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Keywords);
hashCodeCombiner.Add(AcceptTrailingDot);
return hashCodeCombiner;
}
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (AcceptedCharacters == AcceptedCharacters.Any)
{
return PartialParseResult.Rejected;
}
// In some editors intellisense insertions are handled as "dotless commits". If an intellisense selection is confirmed
// via something like '.' a dotless commit will append a '.' and then insert the remaining intellisense selection prior
// to the appended '.'. This 'if' statement attempts to accept the intermediate steps of a dotless commit via
// intellisense. It will accept two cases:
// 1. '@foo.' -> '@foobaz.'.
// 2. '@foobaz..' -> '@foobaz.bar.'. Includes Sub-cases '@foobaz()..' -> '@foobaz().bar.' etc.
// The key distinction being the double '.' in the second case.
if (IsDotlessCommitInsertion(target, normalizedChange))
{
return HandleDotlessCommitInsertion(target);
}
if (IsAcceptableReplace(target, normalizedChange))
{
return HandleReplacement(target, normalizedChange);
}
var changeRelativePosition = normalizedChange.OldPosition - target.Start.AbsoluteIndex;
// Get the edit context
char? lastChar = null;
if (changeRelativePosition > 0 && target.Content.Length > 0)
{
lastChar = target.Content[changeRelativePosition - 1];
}
// Don't support 0->1 length edits
if (lastChar == null)
{
return PartialParseResult.Rejected;
}
// Accepts cases when insertions are made at the end of a span or '.' is inserted within a span.
if (IsAcceptableInsertion(target, normalizedChange))
{
// Handle the insertion
return HandleInsertion(target, lastChar.Value, normalizedChange);
}
if (IsAcceptableDeletion(target, normalizedChange))
{
return HandleDeletion(target, lastChar.Value, normalizedChange);
}
return PartialParseResult.Rejected;
}
// A dotless commit is the process of inserting a '.' with an intellisense selection.
private static bool IsDotlessCommitInsertion(Span target, TextChange change)
{
return IsNewDotlessCommitInsertion(target, change) || IsSecondaryDotlessCommitInsertion(target, change);
}
// Completing 'DateTime' in intellisense with a '.' could result in: '@DateT' -> '@DateT.' -> '@DateTime.' which is accepted.
private static bool IsNewDotlessCommitInsertion(Span target, TextChange change)
{
return !IsAtEndOfSpan(target, change) &&
change.NewPosition > 0 &&
change.NewLength > 0 &&
target.Content.Last() == '.' &&
ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false) &&
(change.OldLength == 0 || ParserHelpers.IsIdentifier(change.OldText, requireIdentifierStart: false));
}
// Once a dotless commit has been performed you then have something like '@DateTime.'. This scenario is used to detect the
// situation when you try to perform another dotless commit resulting in a textchange with '..'. Completing 'DateTime.Now'
// in intellisense with a '.' could result in: '@DateTime.' -> '@DateTime..' -> '@DateTime.Now.' which is accepted.
private static bool IsSecondaryDotlessCommitInsertion(Span target, TextChange change)
{
// Do not need to worry about other punctuation, just looking for double '.' (after change)
return change.NewLength == 1 &&
!string.IsNullOrEmpty(target.Content) &&
target.Content.Last() == '.' &&
change.NewText == "." &&
change.OldLength == 0;
}
private static bool IsAcceptableReplace(Span target, TextChange change)
{
return IsEndReplace(target, change) ||
(change.IsReplace && RemainingIsWhitespace(target, change));
}
private static bool IsAcceptableDeletion(Span target, TextChange change)
{
return IsEndDeletion(target, change) ||
(change.IsDelete && RemainingIsWhitespace(target, change));
}
// Acceptable insertions can occur at the end of a span or when a '.' is inserted within a span.
private static bool IsAcceptableInsertion(Span target, TextChange change)
{
return change.IsInsert &&
(IsAcceptableEndInsertion(target, change) ||
IsAcceptableInnerInsertion(target, change));
}
// Accepts character insertions at the end of spans. AKA: '@foo' -> '@fooo' or '@foo' -> '@foo ' etc.
private static bool IsAcceptableEndInsertion(Span target, TextChange change)
{
Debug.Assert(change.IsInsert);
return IsAtEndOfSpan(target, change) ||
RemainingIsWhitespace(target, change);
}
// Accepts '.' insertions in the middle of spans. Ex: '@foo.baz.bar' -> '@foo..baz.bar'
// This is meant to allow intellisense when editing a span.
private static bool IsAcceptableInnerInsertion(Span target, TextChange change)
{
Debug.Assert(change.IsInsert);
// Ensure that we're actually inserting in the middle of a span and not at the end.
// This case will fail if the IsAcceptableEndInsertion does not capture an end insertion correctly.
Debug.Assert(!IsAtEndOfSpan(target, change));
return change.NewPosition > 0 &&
change.NewText == ".";
}
private static bool RemainingIsWhitespace(Span target, TextChange change)
{
var offset = (change.OldPosition - target.Start.AbsoluteIndex) + change.OldLength;
return string.IsNullOrWhiteSpace(target.Content.Substring(offset));
}
private PartialParseResult HandleDotlessCommitInsertion(Span target)
{
var result = PartialParseResult.Accepted;
if (!AcceptTrailingDot && target.Content.LastOrDefault() == '.')
{
result |= PartialParseResult.Provisional;
}
return result;
}
private PartialParseResult HandleReplacement(Span target, TextChange change)
{
// Special Case for IntelliSense commits.
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
// 1. Insert "." at the end of this span
// 2. Replace the "Date." at the end of the span with "DateTime."
// We need partial parsing to accept case #2.
var oldText = GetOldText(target, change);
var result = PartialParseResult.Rejected;
if (EndsWithDot(oldText) && EndsWithDot(change.NewText))
{
result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
}
return result;
}
private PartialParseResult HandleDeletion(Span target, char previousChar, TextChange change)
{
// What's left after deleting?
if (previousChar == '.')
{
return TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional);
}
else if (ParserHelpers.IsIdentifierPart(previousChar))
{
return TryAcceptChange(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertion(Span target, char previousChar, TextChange change)
{
// What are we inserting after?
if (previousChar == '.')
{
return HandleInsertionAfterDot(target, change);
}
else if (ParserHelpers.IsIdentifierPart(previousChar) || previousChar == ')' || previousChar == ']')
{
return HandleInsertionAfterIdPart(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertionAfterIdPart(Span target, TextChange change)
{
// If the insertion is a full identifier part, accept it
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
{
return TryAcceptChange(target, change);
}
else if (EndsWithDot(change.NewText))
{
// Accept it, possibly provisionally
var result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
return TryAcceptChange(target, change, result);
}
else
{
return PartialParseResult.Rejected;
}
}
private static bool EndsWithDot(string content)
{
return (content.Length == 1 && content[0] == '.') ||
(content[content.Length - 1] == '.' &&
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
}
private PartialParseResult HandleInsertionAfterDot(Span target, TextChange change)
{
// If the insertion is a full identifier or another dot, accept it
if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".")
{
return TryAcceptChange(target, change);
}
return PartialParseResult.Rejected;
}
private PartialParseResult TryAcceptChange(Span target, TextChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
{
var content = change.ApplyChange(target);
if (StartsWithKeyword(content))
{
return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged;
}
return acceptResult;
}
private bool StartsWithKeyword(string newContent)
{
using (var reader = new StringReader(newContent))
{
return _keywords.Contains(reader.ReadWhile(ParserHelpers.IsIdentifierPart));
}
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal enum KnownSymbolType
{
WhiteSpace,
NewLine,
Identifier,
Keyword,
Transition,
Unknown,
CommentStart,
CommentStar,
CommentBody
}
}

View File

@ -0,0 +1,109 @@
// 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.IO;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class LanguageCharacteristics<TTokenizer, TSymbol, TSymbolType>
where TSymbolType : struct
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
public abstract string GetSample(TSymbolType type);
public abstract TTokenizer CreateTokenizer(ITextDocument source);
public abstract TSymbolType FlipBracket(TSymbolType bracket);
public abstract TSymbol CreateMarkerSymbol(SourceLocation location);
public virtual IEnumerable<TSymbol> TokenizeString(string content)
{
return TokenizeString(SourceLocation.Zero, content);
}
public virtual IEnumerable<TSymbol> TokenizeString(SourceLocation start, string input)
{
using (var reader = new SeekableTextReader(input))
{
var tok = CreateTokenizer(reader);
TSymbol sym;
while ((sym = tok.NextSymbol()) != null)
{
sym.OffsetStart(start);
yield return sym;
}
}
}
public virtual bool IsWhiteSpace(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.WhiteSpace);
}
public virtual bool IsNewLine(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.NewLine);
}
public virtual bool IsIdentifier(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Identifier);
}
public virtual bool IsKeyword(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Keyword);
}
public virtual bool IsTransition(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Transition);
}
public virtual bool IsCommentStart(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentStart);
}
public virtual bool IsCommentStar(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentStar);
}
public virtual bool IsCommentBody(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentBody);
}
public virtual bool IsUnknown(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Unknown);
}
public virtual bool IsKnownSymbolType(TSymbol symbol, KnownSymbolType type)
{
return symbol != null && Equals(symbol.Type, GetKnownSymbolType(type));
}
public virtual Tuple<TSymbol, TSymbol> SplitSymbol(TSymbol symbol, int splitAt, TSymbolType leftType)
{
var left = CreateSymbol(symbol.Start, symbol.Content.Substring(0, splitAt), leftType, RazorError.EmptyArray);
TSymbol right = null;
if (splitAt < symbol.Content.Length)
{
right = CreateSymbol(SourceLocationTracker.CalculateNewLocation(symbol.Start, left.Content), symbol.Content.Substring(splitAt), symbol.Type, symbol.Errors);
}
return Tuple.Create(left, right);
}
public abstract TSymbolType GetKnownSymbolType(KnownSymbolType type);
public virtual bool KnowsSymbolType(KnownSymbolType type)
{
return type == KnownSymbolType.Unknown || !Equals(GetKnownSymbolType(type), GetKnownSymbolType(KnownSymbolType.Unknown));
}
protected abstract TSymbol CreateSymbol(SourceLocation location, string content, TSymbolType type, IReadOnlyList<RazorError> errors);
}
}

View File

@ -0,0 +1,165 @@
// 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.Text;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class LineTrackingStringBuffer
{
private readonly IList<TextLine> _lines;
private TextLine _currentLine;
private TextLine _endLine;
public LineTrackingStringBuffer(string content)
{
_endLine = new TextLine(0, 0);
_lines = new List<TextLine>() { _endLine };
Append(content);
}
public int Length
{
get { return _endLine.End; }
}
public SourceLocation EndLocation
{
get { return new SourceLocation(Length, _lines.Count - 1, _lines[_lines.Count - 1].Length); }
}
public CharacterReference CharAt(int absoluteIndex)
{
var line = FindLine(absoluteIndex);
if (line == null)
{
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
}
var idx = absoluteIndex - line.Start;
return new CharacterReference(line.Content[idx], new SourceLocation(absoluteIndex, line.Index, idx));
}
private void Append(string content)
{
for (int i = 0; i < content.Length; i++)
{
AppendCore(content[i]);
// \r on it's own: Start a new line, otherwise wait for \n
// Other Newline: Start a new line
if ((content[i] == '\r' && (i + 1 == content.Length || content[i + 1] != '\n')) || (content[i] != '\r' && ParserHelpers.IsNewLine(content[i])))
{
PushNewLine();
}
}
}
private void PushNewLine()
{
_endLine = new TextLine(_endLine.End, _endLine.Index + 1);
_lines.Add(_endLine);
}
private void AppendCore(char chr)
{
Debug.Assert(_lines.Count > 0);
_lines[_lines.Count - 1].Content.Append(chr);
}
private TextLine FindLine(int absoluteIndex)
{
TextLine selected = null;
if (_currentLine != null)
{
if (_currentLine.Contains(absoluteIndex))
{
// This index is on the last read line
selected = _currentLine;
}
else if (absoluteIndex > _currentLine.Index && _currentLine.Index + 1 < _lines.Count)
{
// This index is ahead of the last read line
selected = ScanLines(absoluteIndex, _currentLine.Index);
}
}
// Have we found a line yet?
if (selected == null)
{
// Scan from line 0
selected = ScanLines(absoluteIndex, 0);
}
Debug.Assert(selected == null || selected.Contains(absoluteIndex));
_currentLine = selected;
return selected;
}
private TextLine ScanLines(int absoluteIndex, int startPos)
{
for (int i = 0; i < _lines.Count; i++)
{
var idx = (i + startPos) % _lines.Count;
Debug.Assert(idx >= 0 && idx < _lines.Count);
if (_lines[idx].Contains(absoluteIndex))
{
return _lines[idx];
}
}
return null;
}
internal struct CharacterReference
{
public CharacterReference(char character, SourceLocation location)
{
Character = character;
Location = location;
}
public char Character { get; }
public SourceLocation Location { get; }
}
private class TextLine
{
private StringBuilder _content = new StringBuilder();
public TextLine(int start, int index)
{
Start = start;
Index = index;
}
public StringBuilder Content
{
get { return _content; }
}
public int Length
{
get { return Content.Length; }
}
public int Start { get; set; }
public int Index { get; set; }
public int End
{
get { return Start + Length; }
}
public bool Contains(int index)
{
return index < End && index >= Start;
}
}
}
}

View File

@ -0,0 +1,81 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class LiteralAttributeChunkGenerator : SpanChunkGenerator
{
public LiteralAttributeChunkGenerator(
LocationTagged<string> prefix,
LocationTagged<SpanChunkGenerator> valueGenerator)
{
Prefix = prefix;
ValueGenerator = valueGenerator;
}
public LiteralAttributeChunkGenerator(LocationTagged<string> prefix, LocationTagged<string> value)
{
Prefix = prefix;
Value = value;
}
public LocationTagged<string> Prefix { get; }
public LocationTagged<string> Value { get; }
public LocationTagged<SpanChunkGenerator> ValueGenerator { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//var chunk = context.ChunkTreeBuilder.StartParentChunk<LiteralCodeAttributeChunk>(target);
//chunk.Prefix = Prefix;
//chunk.Value = Value;
//if (ValueGenerator != null)
//{
// chunk.ValueLocation = ValueGenerator.Location;
// ValueGenerator.Value.GenerateChunk(target, context);
// chunk.ValueLocation = ValueGenerator.Location;
//}
//context.ChunkTreeBuilder.EndParentChunk();
}
public override string ToString()
{
if (ValueGenerator == null)
{
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},{1:F}", Prefix, Value);
}
else
{
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},<Sub:{1:F}>", Prefix, ValueGenerator);
}
}
public override bool Equals(object obj)
{
var other = obj as LiteralAttributeChunkGenerator;
return other != null &&
Equals(other.Prefix, Prefix) &&
Equals(other.Value, Value) &&
Equals(other.ValueGenerator, ValueGenerator);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Prefix);
hashCodeCombiner.Add(Value);
hashCodeCombiner.Add(ValueGenerator);
return hashCodeCombiner;
}
}
}

View File

@ -0,0 +1,84 @@
// 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.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
[DebuggerDisplay("({Location})\"{Value}\"")]
internal class LocationTagged<TValue> : IFormattable
{
public LocationTagged(TValue value, int absoluteIndex, int lineIndex, int characterIndex)
: this (value, new SourceLocation(absoluteIndex, lineIndex, characterIndex))
{
}
public LocationTagged(TValue value, SourceLocation location)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
Location = location;
Value = value;
}
public SourceLocation Location { get; }
public TValue Value { get; }
public override bool Equals(object obj)
{
var other = obj as LocationTagged<TValue>;
if (ReferenceEquals(other, null))
{
return false;
}
return Equals(other.Location, Location) &&
Equals(other.Value, Value);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Location);
hashCodeCombiner.Add(Value);
return hashCodeCombiner.CombinedHash;
}
public override string ToString()
{
return Value.ToString();
}
public string ToString(string format, IFormatProvider formatProvider)
{
if (string.IsNullOrEmpty(format))
{
format = "P";
}
if (formatProvider == null)
{
formatProvider = CultureInfo.CurrentCulture;
}
switch (format.ToUpperInvariant())
{
case "F":
return string.Format(formatProvider, "{0}@{1}", Value, Location);
default:
return Value.ToString();
}
}
public static implicit operator TValue(LocationTagged<TValue> value)
{
return value == null ? default(TValue) : value.Value;
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class MarkupChunkGenerator : SpanChunkGenerator
{
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddLiteralChunk(target.Content, target);
}
public override string ToString()
{
return "Markup";
}
}
}

View File

@ -0,0 +1,47 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class ParentChunkGenerator : IParentChunkGenerator
{
private static readonly int TypeHashCode = typeof(ParentChunkGenerator).GetHashCode();
public static readonly IParentChunkGenerator Null = new NullParentChunkGenerator();
public virtual void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
}
public virtual void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
}
public override bool Equals(object obj)
{
return obj != null &&
GetType() == obj.GetType();
}
public override int GetHashCode()
{
return TypeHashCode;
}
private class NullParentChunkGenerator : IParentChunkGenerator
{
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
}
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
}
public override string ToString()
{
return "None";
}
}
}
}

View File

@ -0,0 +1,21 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class ParserBase
{
private ParserContext _context;
public ParserBase(ParserContext context)
{
Context = context;
}
public ParserContext Context { get; }
public abstract void BuildSpan(SpanBuilder span, SourceLocation start, string content);
public abstract void ParseBlock();
}
}

View File

@ -0,0 +1,98 @@
// 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.Diagnostics;
using System.IO;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal partial class ParserContext
{
public ParserContext(ITextDocument source, bool designTime)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
Source = source;
DesignTimeMode = designTime;
Builder = new SyntaxTreeBuilder();
ErrorSink = new ErrorSink();
}
public SyntaxTreeBuilder Builder { get; }
public ErrorSink ErrorSink { get; }
public ITextDocument Source { get; }
public bool DesignTimeMode { get; }
public bool WhiteSpaceIsSignificantToAncestorBlock { get; set; }
public bool NullGenerateWhitespaceAndNewLine { get; set; }
public bool EndOfFile
{
get { return Source.Peek() == -1; }
}
public RazorSyntaxTree BuildRazorSyntaxTree()
{
var syntaxTree = Builder.Build();
var razorSyntaxTree = RazorSyntaxTree.Create(syntaxTree, ErrorSink.Errors);
return razorSyntaxTree;
}
}
// Debug Helpers
#if DEBUG
[DebuggerDisplay("{Unparsed}")]
internal partial class ParserContext
{
private const int InfiniteLoopCountThreshold = 1000;
private int _infiniteLoopGuardCount = 0;
private SourceLocation? _infiniteLoopGuardLocation = null;
internal string Unparsed
{
get
{
var remaining = ((TextReader)Source).ReadToEnd();
Source.Position -= remaining.Length;
return remaining;
}
}
private bool CheckInfiniteLoop()
{
// Infinite loop guard
// Basically, if this property is accessed 1000 times in a row without having advanced the source reader to the next position, we
// cause a parser error
if (_infiniteLoopGuardLocation != null)
{
if (Source.Location.Equals(_infiniteLoopGuardLocation.Value))
{
_infiniteLoopGuardCount++;
if (_infiniteLoopGuardCount > InfiniteLoopCountThreshold)
{
Debug.Fail("An internal parser error is causing an infinite loop at this location.");
return true;
}
}
else
{
_infiniteLoopGuardCount = 0;
}
}
_infiniteLoopGuardLocation = Source.Location;
return false;
}
}
#endif
}

View File

@ -0,0 +1,100 @@
// 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.Globalization;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal static class ParserHelpers
{
public static bool IsNewLine(char value)
{
return value == '\r' // Carriage return
|| value == '\n' // Linefeed
|| value == '\u0085' // Next Line
|| value == '\u2028' // Line separator
|| value == '\u2029'; // Paragraph separator
}
public static bool IsNewLine(string value)
{
return (value.Length == 1 && (IsNewLine(value[0]))) ||
(string.Equals(value, Environment.NewLine, StringComparison.Ordinal));
}
public static bool IsIdentifier(string value)
{
return IsIdentifier(value, requireIdentifierStart: true);
}
public static bool IsIdentifier(string value, bool requireIdentifierStart)
{
IEnumerable<char> identifierPart = value;
if (requireIdentifierStart)
{
identifierPart = identifierPart.Skip(1);
}
return (!requireIdentifierStart || IsIdentifierStart(value[0])) && identifierPart.All(IsIdentifierPart);
}
public static bool IsIdentifierStart(char value)
{
return value == '_' || IsLetter(value);
}
public static bool IsIdentifierPart(char value)
{
return IsLetter(value)
|| IsDecimalDigit(value)
|| IsConnecting(value)
|| IsCombining(value)
|| IsFormatting(value);
}
public static bool IsFormatting(char value)
{
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.Format;
}
public static bool IsCombining(char value)
{
var cat = CharUnicodeInfo.GetUnicodeCategory(value);
return cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.NonSpacingMark;
}
public static bool IsConnecting(char value)
{
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.ConnectorPunctuation;
}
public static bool IsWhitespace(char value)
{
return value == ' ' ||
value == '\f' ||
value == '\t' ||
value == '\u000B' || // Vertical Tab
CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.SpaceSeparator;
}
public static bool IsLetter(char value)
{
var cat = CharUnicodeInfo.GetUnicodeCategory(value);
return cat == UnicodeCategory.UppercaseLetter
|| cat == UnicodeCategory.LowercaseLetter
|| cat == UnicodeCategory.TitlecaseLetter
|| cat == UnicodeCategory.ModifierLetter
|| cat == UnicodeCategory.OtherLetter
|| cat == UnicodeCategory.LetterNumber;
}
public static bool IsDecimalDigit(char value)
{
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.DecimalDigitNumber;
}
}
}

View File

@ -0,0 +1,59 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// Flags:
// Provisional, ContextChanged, Accepted, Rejected
// 000001 1 - Rejected,
// 000010 2 - Accepted
// 000100 4 - Provisional
// 001000 8 - Context Changed
// 010000 16 - Auto Complete Block
/// <summary>
/// The result of attempting an incremental parse
/// </summary>
/// <remarks>
/// Either the Accepted or Rejected flag is ALWAYS set.
/// Additionally, Provisional may be set with Accepted and SpanContextChanged may be set with Rejected.
/// Provisional may NOT be set with Rejected and SpanContextChanged may NOT be set with Accepted.
/// </remarks>
[Flags]
internal enum PartialParseResult
{
/// <summary>
/// Indicates that the edit could not be accepted and that a reparse is underway.
/// </summary>
Rejected = 1,
/// <summary>
/// Indicates that the edit was accepted and has been added to the parse tree
/// </summary>
Accepted = 2,
/// <summary>
/// Indicates that the edit was accepted, but that a reparse should be forced when idle time is available
/// since the edit may be misclassified
/// </summary>
/// <remarks>
/// This generally occurs when a "." is typed in an Implicit Expression, since editors require that this
/// be assigned to Code in order to properly support features like IntelliSense. However, if no further edits
/// occur following the ".", it should be treated as Markup.
/// </remarks>
Provisional = 4,
/// <summary>
/// Indicates that the edit caused a change in the span's context and that if any statement completions were active prior to starting this
/// partial parse, they should be reinitialized.
/// </summary>
SpanContextChanged = 8,
/// <summary>
/// Indicates that the edit requires an auto completion to occur
/// </summary>
AutoCompleteBlock = 16
}
}

View File

@ -0,0 +1,9 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class RazorCommentChunkGenerator : ParentChunkGenerator
{
}
}

View File

@ -0,0 +1,80 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class RazorError : IEquatable<RazorError>
{
internal static readonly RazorError[] EmptyArray = new RazorError[0];
/// <summary>
/// Used only for deserialization.
/// </summary>
public RazorError()
: this(message: string.Empty, location: SourceLocation.Undefined, length: -1)
{
}
public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex, int length)
: this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex), length)
{
}
public RazorError(string message, SourceLocation location, int length)
{
Message = message;
Location = location;
Length = length;
}
/// <summary>
/// Gets (or sets) the message describing the error.
/// </summary>
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public string Message { get; set; }
/// <summary>
/// Gets (or sets) the start position of the erroneous text.
/// </summary>
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public SourceLocation Location { get; set; }
/// <summary>
/// Gets or sets the length of the erroneous text.
/// </summary>
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int Length { get; set; }
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "Error @ {0}({2}) - [{1}]", Location, Message, Length);
}
public override bool Equals(object obj)
{
var error = obj as RazorError;
return Equals(error);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Message, StringComparer.Ordinal);
hashCodeCombiner.Add(Location);
return hashCodeCombiner;
}
public bool Equals(RazorError other)
{
return other != null &&
string.Equals(other.Message, Message, StringComparison.Ordinal) &&
Location.Equals(other.Location) &&
Length.Equals(other.Length);
}
}
}

View File

@ -0,0 +1,48 @@
// 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.IO;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class RazorParser
{
public RazorParser()
{
}
public bool DesignTimeMode { get; set; }
public virtual RazorSyntaxTree Parse(TextReader input)
{
var reader = new SeekableTextReader(input);
return Parse((ITextDocument)reader);
}
public virtual RazorSyntaxTree Parse(ITextDocument input)
{
return ParseCore(input);
}
private RazorSyntaxTree ParseCore(ITextDocument input)
{
var context = new ParserContext(input, DesignTimeMode);
var codeParser = new CSharpCodeParser(context);
var markupParser = new HtmlMarkupParser(context);
codeParser.HtmlParser = markupParser;
markupParser.CodeParser = codeParser;
// Execute the parse
markupParser.ParseDocument();
// Get the result
var razorSyntaxTree = context.BuildRazorSyntaxTree();
// Return the new result
return razorSyntaxTree;
}
}
}

View File

@ -0,0 +1,41 @@
// 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 Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class RemoveTagHelperChunkGenerator : SpanChunkGenerator
{
public RemoveTagHelperChunkGenerator(string lookupText)
{
LookupText = lookupText;
}
public string LookupText { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
var other = obj as RemoveTagHelperChunkGenerator;
return base.Equals(other) &&
string.Equals(LookupText, other.LookupText, StringComparison.Ordinal);
}
/// <inheritdoc />
public override int GetHashCode()
{
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(LookupText, StringComparer.Ordinal);
return combiner.CombinedHash;
}
}
}

View File

@ -0,0 +1,46 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SectionChunkGenerator : ParentChunkGenerator
{
public SectionChunkGenerator(string sectionName)
{
SectionName = sectionName;
}
public string SectionName { get; }
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//var chunk = context.ChunkTreeBuilder.StartParentChunk<SectionChunk>(target);
//chunk.Name = SectionName;
}
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.EndParentChunk();
}
public override bool Equals(object obj)
{
var other = obj as SectionChunkGenerator;
return base.Equals(other) &&
string.Equals(SectionName, other.SectionName, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return SectionName == null ? 0 : StringComparer.Ordinal.GetHashCode(SectionName);
}
public override string ToString()
{
return "Section:" + SectionName;
}
}
}

View File

@ -0,0 +1,90 @@
// 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.IO;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SeekableTextReader : TextReader, ITextDocument
{
private readonly LineTrackingStringBuffer _buffer;
private int _position = 0;
private SourceLocation _location = SourceLocation.Zero;
private char? _current;
public SeekableTextReader(TextReader source)
: this(source.ReadToEnd())
{
}
public SeekableTextReader(string source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
_buffer = new LineTrackingStringBuffer(source);
UpdateState();
}
public SourceLocation Location => _location;
public int Length => _buffer.Length;
public int Position
{
get { return _position; }
set
{
if (_position != value)
{
_position = value;
UpdateState();
}
}
}
public override int Read()
{
if (_current == null)
{
return -1;
}
var chr = _current.Value;
_position++;
UpdateState();
return chr;
}
public override int Peek()
{
if (_current == null)
{
return -1;
}
return _current.Value;
}
private void UpdateState()
{
if (_position < _buffer.Length)
{
var chr = _buffer.CharAt(_position);
_current = chr.Character;
_location = chr.Location;
}
else if (_buffer.Length == 0)
{
_current = null;
_location = SourceLocation.Zero;
}
else
{
_current = null;
_location = _buffer.EndLocation;
}
}
}
}

View File

@ -0,0 +1,38 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SetBaseTypeChunkGenerator : SpanChunkGenerator
{
public SetBaseTypeChunkGenerator(string baseType)
{
BaseType = baseType;
}
public string BaseType { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
}
public override string ToString()
{
return "Base:" + BaseType;
}
public override bool Equals(object obj)
{
var other = obj as SetBaseTypeChunkGenerator;
return other != null &&
string.Equals(BaseType, other.BaseType, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return BaseType.GetHashCode();
}
}
}

View File

@ -0,0 +1,194 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
/// <summary>
/// A location in a Razor file.
/// </summary>
internal struct SourceLocation : IEquatable<SourceLocation>
{
/// <summary>
/// An undefined <see cref="SourceLocation"/>.
/// </summary>
public static readonly SourceLocation Undefined =
new SourceLocation(absoluteIndex: -1, lineIndex: -1, characterIndex: -1);
/// <summary>
/// A <see cref="SourceLocation"/> with <see cref="AbsoluteIndex"/>, <see cref="LineIndex"/>, and
/// <see cref="CharacterIndex"/> initialized to 0.
/// </summary>
public static readonly SourceLocation Zero =
new SourceLocation(absoluteIndex: 0, lineIndex: 0, characterIndex: 0);
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(int absoluteIndex, int lineIndex, int characterIndex)
: this(filePath: null, absoluteIndex: absoluteIndex, lineIndex: lineIndex, characterIndex: characterIndex)
{
}
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(string filePath, int absoluteIndex, int lineIndex, int characterIndex)
{
FilePath = filePath;
AbsoluteIndex = absoluteIndex;
LineIndex = lineIndex;
CharacterIndex = characterIndex;
}
/// <summary>
/// Path of the file.
/// </summary>
/// <remarks>When <c>null</c>, the parser assumes the location is in the file currently being processed.
/// </remarks>
public string FilePath { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int AbsoluteIndex { get; set; }
/// <summary>
/// Gets the 1-based index of the line referred to by this Source Location.
/// </summary>
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int LineIndex { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int CharacterIndex { get; set; }
/// <inheritdoc />
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"({0}:{1},{2})",
AbsoluteIndex,
LineIndex,
CharacterIndex);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is SourceLocation &&
Equals((SourceLocation)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(FilePath, StringComparer.Ordinal);
hashCodeCombiner.Add(AbsoluteIndex);
return hashCodeCombiner;
}
/// <inheritdoc />
public bool Equals(SourceLocation other)
{
// LineIndex and CharacterIndex can be calculated from AbsoluteIndex and the document content.
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
AbsoluteIndex == other.AbsoluteIndex;
}
/// <summary>
/// Advances the <see cref="SourceLocation"/> by the length of the <paramref name="text" />.
/// </summary>
/// <param name="left">The <see cref="SourceLocation"/> to advance.</param>
/// <param name="text">The <see cref="string"/> to advance <paramref name="left"/> by.</param>
/// <returns>The advanced <see cref="SourceLocation"/>.</returns>
public static SourceLocation Advance(SourceLocation left, string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var tracker = new SourceLocationTracker(left);
tracker.UpdateLocation(text);
return tracker.CurrentLocation;
}
/// <summary>
/// Adds two <see cref="SourceLocation"/>s.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>A <see cref="SourceLocation"/> that is the sum of the left and right operands.</returns>
/// <exception cref="ArgumentException">if the <see cref="FilePath"/> of the left and right operands
/// are different, and neither is null.</exception>
public static SourceLocation operator +(SourceLocation left, SourceLocation right)
{
if (!string.Equals(left.FilePath, right.FilePath, StringComparison.Ordinal) &&
left.FilePath != null &&
right.FilePath != null)
{
// Throw if FilePath for left and right are different, and neither is null.
throw new ArgumentException(
LegacyResources.FormatSourceLocationFilePathDoesNotMatch(nameof(SourceLocation), "+"),
nameof(right));
}
var resultFilePath = left.FilePath ?? right.FilePath;
if (right.LineIndex > 0)
{
// Column index doesn't matter
return new SourceLocation(
resultFilePath,
left.AbsoluteIndex + right.AbsoluteIndex,
left.LineIndex + right.LineIndex,
right.CharacterIndex);
}
else
{
return new SourceLocation(
resultFilePath,
left.AbsoluteIndex + right.AbsoluteIndex,
left.LineIndex + right.LineIndex,
left.CharacterIndex + right.CharacterIndex);
}
}
/// <summary>
/// Subtracts two <see cref="SourceLocation"/>s.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>A <see cref="SourceLocation"/> that is the difference of the left and right operands.</returns>
/// <exception cref="ArgumentException">if the <see cref="FilePath"/> of the left and right operands
/// are different.</exception>
public static SourceLocation operator -(SourceLocation left, SourceLocation right)
{
if (!string.Equals(left.FilePath, right.FilePath, StringComparison.Ordinal))
{
throw new ArgumentException(
LegacyResources.FormatSourceLocationFilePathDoesNotMatch(nameof(SourceLocation), "-"),
nameof(right));
}
var characterIndex = left.LineIndex != right.LineIndex ?
left.CharacterIndex : left.CharacterIndex - right.CharacterIndex;
return new SourceLocation(
filePath: null,
absoluteIndex: left.AbsoluteIndex - right.AbsoluteIndex,
lineIndex: left.LineIndex - right.LineIndex,
characterIndex: characterIndex);
}
}
}

View File

@ -0,0 +1,101 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SourceLocationTracker
{
private int _absoluteIndex = 0;
private int _characterIndex = 0;
private int _lineIndex = 0;
private SourceLocation _currentLocation;
public SourceLocationTracker()
: this(SourceLocation.Zero)
{
}
public SourceLocationTracker(SourceLocation currentLocation)
{
CurrentLocation = currentLocation;
UpdateInternalState();
}
public SourceLocation CurrentLocation
{
get
{
return _currentLocation;
}
set
{
if (!_currentLocation.Equals(value))
{
_currentLocation = value;
UpdateInternalState();
}
}
}
public void UpdateLocation(char characterRead, char nextCharacter)
{
UpdateCharacterCore(characterRead, nextCharacter);
RecalculateSourceLocation();
}
public SourceLocationTracker UpdateLocation(string content)
{
for (int i = 0; i < content.Length; i++)
{
var nextCharacter = '\0';
if (i < content.Length - 1)
{
nextCharacter = content[i + 1];
}
UpdateCharacterCore(content[i], nextCharacter);
}
RecalculateSourceLocation();
return this;
}
private void UpdateCharacterCore(char characterRead, char nextCharacter)
{
_absoluteIndex++;
if (Environment.NewLine.Length == 1 && characterRead == Environment.NewLine[0] ||
ParserHelpers.IsNewLine(characterRead) && (characterRead != '\r' || nextCharacter != '\n'))
{
_lineIndex++;
_characterIndex = 0;
}
else
{
_characterIndex++;
}
}
private void UpdateInternalState()
{
_absoluteIndex = CurrentLocation.AbsoluteIndex;
_characterIndex = CurrentLocation.CharacterIndex;
_lineIndex = CurrentLocation.LineIndex;
}
private void RecalculateSourceLocation()
{
_currentLocation = new SourceLocation(
_currentLocation.FilePath,
_absoluteIndex,
_lineIndex,
_characterIndex);
}
public static SourceLocation CalculateNewLocation(SourceLocation lastPosition, string newContent)
{
return new SourceLocationTracker(lastPosition).UpdateLocation(newContent).CurrentLocation;
}
}
}

View File

@ -0,0 +1,135 @@
// 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.Linq;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class Span : SyntaxTreeNode
{
private static readonly int TypeHashCode = typeof(Span).GetHashCode();
private string _content;
private SourceLocation _start;
public Span(SpanBuilder builder)
{
ReplaceWith(builder);
}
public ISpanChunkGenerator ChunkGenerator { get; private set; }
public SpanKind Kind { get; private set; }
public IReadOnlyList<ISymbol> Symbols { get; private set; }
// Allow test code to re-link spans
public Span Previous { get; internal set; }
public Span Next { get; internal set; }
public SpanEditHandler EditHandler { get; private set; }
public override bool IsBlock => false;
public override int Length => Content.Length;
public override SourceLocation Start => _start;
public string Content
{
get
{
if (_content == null)
{
var builder = new StringBuilder();
for (var i = 0; i < Symbols.Count; i++)
{
var symbol = Symbols[i];
builder.Append(symbol.Content);
}
_content = builder.ToString();
}
return _content;
}
}
public void ReplaceWith(SpanBuilder builder)
{
Kind = builder.Kind;
Symbols = builder.Symbols;
EditHandler = builder.EditHandler;
ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null;
_start = builder.Start;
_content = null;
// Since we took references to the values in SpanBuilder, clear its references out
builder.Reset();
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(Kind);
builder.AppendFormat(" Span at {0}::{1} - [{2}]", Start, Length, Content);
builder.Append(" Edit: <");
builder.Append(EditHandler.ToString());
builder.Append("> Gen: <");
builder.Append(ChunkGenerator.ToString());
builder.Append("> {");
builder.Append(string.Join(";", Symbols.GroupBy(sym => sym.GetType()).Select(grp => string.Concat(grp.Key.Name, ":", grp.Count()))));
builder.Append("}");
return builder.ToString();
}
public void ChangeStart(SourceLocation newStart)
{
_start = newStart;
var current = this;
var tracker = new SourceLocationTracker(newStart);
tracker.UpdateLocation(Content);
while ((current = current.Next) != null)
{
current._start = tracker.CurrentLocation;
tracker.UpdateLocation(current.Content);
}
}
/// <summary>
/// Checks that the specified span is equivalent to the other in that it has the same start point and content.
/// </summary>
public override bool EquivalentTo(SyntaxTreeNode node)
{
var other = node as Span;
return other != null &&
Kind.Equals(other.Kind) &&
Start.Equals(other.Start) &&
EditHandler.Equals(other.EditHandler) &&
string.Equals(other.Content, Content, StringComparison.Ordinal);
}
public override int GetEquivalenceHash()
{
// Hash code should include only immutable properties but EquivalentTo also checks the type.
return TypeHashCode;
}
public override bool Equals(object obj)
{
var other = obj as Span;
return other != null &&
Kind.Equals(other.Kind) &&
EditHandler.Equals(other.EditHandler) &&
ChunkGenerator.Equals(other.ChunkGenerator) &&
Symbols.SequenceEqual(other.Symbols);
}
public override int GetHashCode()
{
// Hash code should include only immutable properties but Equals also checks the type.
return TypeHashCode;
}
}
}

View File

@ -0,0 +1,92 @@
// 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 System.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SpanBuilder
{
private List<ISymbol> _symbols;
private SourceLocationTracker _tracker = new SourceLocationTracker();
public SpanBuilder(Span original)
{
Kind = original.Kind;
_symbols = new List<ISymbol>(original.Symbols);
EditHandler = original.EditHandler;
Start = original.Start;
ChunkGenerator = original.ChunkGenerator;
}
public SpanBuilder()
{
Reset();
}
public ISpanChunkGenerator ChunkGenerator { get; set; }
public SourceLocation Start { get; set; }
public SpanKind Kind { get; set; }
public IReadOnlyList<ISymbol> Symbols
{
get
{
if (_symbols == null)
{
_symbols = new List<ISymbol>();
}
return _symbols;
}
}
public SpanEditHandler EditHandler { get; set; }
public void Reset()
{
// Need to potentially allocate a new list because Span.ReplaceWith takes ownership
// of the original list.
_symbols = null;
EditHandler = SpanEditHandler.CreateDefault((content) => Enumerable.Empty<ISymbol>());
ChunkGenerator = SpanChunkGenerator.Null;
Start = SourceLocation.Zero;
}
public Span Build()
{
return new Span(this);
}
public void ClearSymbols()
{
_symbols?.Clear();
}
public void Accept(ISymbol symbol)
{
if (symbol == null)
{
return;
}
if (Symbols.Count == 0)
{
Start = symbol.Start;
symbol.ChangeStart(SourceLocation.Zero);
_tracker.CurrentLocation = SourceLocation.Zero;
}
else
{
symbol.ChangeStart(_tracker.CurrentLocation);
}
_symbols.Add(symbol);
_tracker.UpdateLocation(symbol.Content);
}
}
}

View File

@ -0,0 +1,39 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class SpanChunkGenerator : ISpanChunkGenerator
{
private static readonly int TypeHashCode = typeof(SpanChunkGenerator).GetHashCode();
public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator();
public virtual void GenerateChunk(Span target, ChunkGeneratorContext context)
{
}
public override bool Equals(object obj)
{
return obj != null &&
GetType() == obj.GetType();
}
public override int GetHashCode()
{
return TypeHashCode;
}
private class NullSpanChunkGenerator : ISpanChunkGenerator
{
public void GenerateChunk(Span target, ChunkGeneratorContext context)
{
}
public override string ToString()
{
return "None";
}
}
}
}

View File

@ -0,0 +1,143 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SpanEditHandler
{
private static readonly int TypeHashCode = typeof(SpanEditHandler).GetHashCode();
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: this(tokenizer, AcceptedCharacters.Any)
{
}
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
{
AcceptedCharacters = accepted;
Tokenizer = tokenizer;
}
public AcceptedCharacters AcceptedCharacters { get; set; }
public Func<string, IEnumerable<ISymbol>> Tokenizer { get; set; }
public static SpanEditHandler CreateDefault(Func<string, IEnumerable<ISymbol>> tokenizer)
{
return new SpanEditHandler(tokenizer);
}
public virtual EditResult ApplyChange(Span target, TextChange change)
{
return ApplyChange(target, change, force: false);
}
public virtual EditResult ApplyChange(Span target, TextChange change, bool force)
{
var result = PartialParseResult.Accepted;
var normalized = change.Normalize();
if (!force)
{
result = CanAcceptChange(target, normalized);
}
// If the change is accepted then apply the change
if ((result & PartialParseResult.Accepted) == PartialParseResult.Accepted)
{
return new EditResult(result, UpdateSpan(target, normalized));
}
return new EditResult(result, new SpanBuilder(target));
}
public virtual bool OwnsChange(Span target, TextChange change)
{
var end = target.Start.AbsoluteIndex + target.Length;
var changeOldEnd = change.OldPosition + change.OldLength;
return change.OldPosition >= target.Start.AbsoluteIndex &&
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharacters.None));
}
protected virtual PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
return PartialParseResult.Rejected;
}
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
{
var newContent = normalizedChange.ApplyChange(target);
var newSpan = new SpanBuilder(target);
newSpan.ClearSymbols();
foreach (ISymbol sym in Tokenizer(newContent))
{
sym.OffsetStart(target.Start);
newSpan.Accept(sym);
}
if (target.Next != null)
{
var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
target.Next.ChangeStart(newEnd);
}
return newSpan;
}
protected internal static bool IsAtEndOfFirstLine(Span target, TextChange change)
{
var endOfFirstLine = target.Content.IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
return (endOfFirstLine == -1 || (change.OldPosition - target.Start.AbsoluteIndex) <= endOfFirstLine);
}
/// <summary>
/// Returns true if the specified change is an insertion of text at the end of this span.
/// </summary>
protected internal static bool IsEndDeletion(Span target, TextChange change)
{
return change.IsDelete && IsAtEndOfSpan(target, change);
}
/// <summary>
/// Returns true if the specified change is a replacement of text at the end of this span.
/// </summary>
protected internal static bool IsEndReplace(Span target, TextChange change)
{
return change.IsReplace && IsAtEndOfSpan(target, change);
}
protected internal static bool IsAtEndOfSpan(Span target, TextChange change)
{
return (change.OldPosition + change.OldLength) == (target.Start.AbsoluteIndex + target.Length);
}
/// <summary>
/// Returns the old text referenced by the change.
/// </summary>
/// <remarks>
/// If the content has already been updated by applying the change, this data will be _invalid_
/// </remarks>
protected internal static string GetOldText(Span target, TextChange change)
{
return target.Content.Substring(change.OldPosition - target.Start.AbsoluteIndex, change.OldLength);
}
public override string ToString()
{
return GetType().Name + ";Accepts:" + AcceptedCharacters;
}
public override bool Equals(object obj)
{
var other = obj as SpanEditHandler;
return other != null &&
GetType() == other.GetType() &&
AcceptedCharacters == other.AcceptedCharacters;
}
public override int GetHashCode()
{
// Hash code should include only immutable properties but Equals also checks the type.
return TypeHashCode;
}
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal enum SpanKind
{
Transition,
MetaCode,
Comment,
Code,
Markup
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class StatementChunkGenerator : SpanChunkGenerator
{
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddStatementChunk(target.Content, target);
}
public override string ToString()
{
return "Stmt";
}
}
}

View File

@ -0,0 +1,72 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class SymbolBase<TType> : ISymbol where TType : struct
{
protected SymbolBase(
SourceLocation start,
string content,
TType type,
IReadOnlyList<RazorError> errors)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
Start = start;
Content = content;
Type = type;
Errors = errors;
}
public SourceLocation Start { get; private set; }
public IReadOnlyList<RazorError> Errors { get; }
public string Content { get; }
public TType Type { get; }
public override bool Equals(object obj)
{
var other = obj as SymbolBase<TType>;
return other != null &&
Start.Equals(other.Start) &&
string.Equals(Content, other.Content, StringComparison.Ordinal) &&
Type.Equals(other.Type);
}
public override int GetHashCode()
{
// Hash code should include only immutable properties.
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Content, StringComparer.Ordinal);
hashCodeCombiner.Add(Type);
return hashCodeCombiner;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0} {1} - [{2}]", Start, Type, Content);
}
public void OffsetStart(SourceLocation documentStart)
{
Start = documentStart + Start;
}
public void ChangeStart(SourceLocation newStart)
{
Start = newStart;
}
}
}

View File

@ -0,0 +1,39 @@
// 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.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal static class SymbolExtensions
{
public static LocationTagged<string> GetContent(this SpanBuilder span)
{
return GetContent(span, e => e);
}
public static LocationTagged<string> GetContent(this SpanBuilder span, Func<IEnumerable<ISymbol>, IEnumerable<ISymbol>> filter)
{
return GetContent(filter(span.Symbols), span.Start);
}
public static LocationTagged<string> GetContent(this IEnumerable<ISymbol> symbols, SourceLocation spanStart)
{
if (symbols.Any())
{
return new LocationTagged<string>(string.Concat(symbols.Select(s => s.Content)), spanStart + symbols.First().Start);
}
else
{
return new LocationTagged<string>(string.Empty, spanStart);
}
}
public static LocationTagged<string> GetContent(this ISymbol symbol)
{
return new LocationTagged<string>(symbol.Content, symbol.Start);
}
}
}

View File

@ -0,0 +1,31 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal static class SyntaxConstants
{
public static readonly string TextTagName = "text";
public static readonly char TransitionCharacter = '@';
public static readonly string TransitionString = "@";
public static readonly string StartCommentSequence = "@*";
public static readonly string EndCommentSequence = "*@";
public static class CSharp
{
public static readonly int UsingKeywordLength = 5;
public static readonly string TagHelperPrefixKeyword = "tagHelperPrefix";
public static readonly string AddTagHelperKeyword = "addTagHelper";
public static readonly string RemoveTagHelperKeyword = "removeTagHelper";
public static readonly string InheritsKeyword = "inherits";
public static readonly string FunctionsKeyword = "functions";
public static readonly string SectionKeyword = "section";
public static readonly string ElseIfKeyword = "else if";
public static readonly string NamespaceKeyword = "namespace";
public static readonly string ClassKeyword = "class";
// Not supported. Only used for error cases.
public static readonly string HelperKeyword = "helper";
}
}
}

View File

@ -0,0 +1,94 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class SyntaxTreeBuilder
{
private readonly Stack<BlockBuilder> _blockStack;
private readonly Action _endBlock;
public SyntaxTreeBuilder()
{
_blockStack = new Stack<BlockBuilder>();
_endBlock = EndBlock;
}
public IEnumerable<BlockBuilder> ActiveBlocks => _blockStack;
public BlockBuilder CurrentBlock => _blockStack.Peek();
public Span LastSpan { get; private set; }
public AcceptedCharacters LastAcceptedCharacters
{
get
{
if (LastSpan == null)
{
return AcceptedCharacters.None;
}
return LastSpan.EditHandler.AcceptedCharacters;
}
}
public void Add(Span span)
{
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(LegacyResources.ParserContext_NoCurrentBlock);
}
CurrentBlock.Children.Add(span);
LastSpan = span;
}
/// <summary>
/// Starts a block of the specified type
/// </summary>
/// <param name="blockType">The type of the block to start</param>
public IDisposable StartBlock(BlockType blockType)
{
var builder = new BlockBuilder() { Type = blockType };
_blockStack.Push(builder);
return new DisposableAction(_endBlock);
}
/// <summary>
/// Ends the current block
/// </summary>
public void EndBlock()
{
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(LegacyResources.EndBlock_Called_Without_Matching_StartBlock);
}
if (_blockStack.Count > 1)
{
var initialBlockBuilder = _blockStack.Pop();
var initialBlock = initialBlockBuilder.Build();
CurrentBlock.Children.Add(initialBlock);
}
}
public Block Build()
{
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(LegacyResources.ParserContext_CannotCompleteTree_NoRootBlock);
}
if (_blockStack.Count != 1)
{
throw new InvalidOperationException(LegacyResources.ParserContext_CannotCompleteTree_OutstandingBlocks);
}
var rootBuilder = _blockStack.Pop();
var root = rootBuilder.Build();
return root;
}
}
}

View File

@ -0,0 +1,45 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract class SyntaxTreeNode
{
public Block Parent { get; internal set; }
/// <summary>
/// Returns true if this element is a block (to avoid casting)
/// </summary>
public abstract bool IsBlock { get; }
/// <summary>
/// The length of all the content contained in this node
/// </summary>
public abstract int Length { get; }
/// <summary>
/// The start point of this node
/// </summary>
public abstract SourceLocation Start { get; }
/// <summary>
/// Determines if the specified node is equivalent to this node
/// </summary>
/// <param name="node">The node to compare this node with</param>
/// <returns>
/// true if the provided node has all the same content and metadata, though the specific quantity and type of
/// symbols may be different.
/// </returns>
public abstract bool EquivalentTo(SyntaxTreeNode node);
/// <summary>
/// Determines a hash code for the <see cref="SyntaxTreeNode"/> using only information relevant in
/// <see cref="EquivalentTo"/> comparisons.
/// </summary>
/// <returns>
/// A hash code for the <see cref="SyntaxTreeNode"/> using only information relevant in
/// <see cref="EquivalentTo"/> comparisons.
/// </returns>
public abstract int GetEquivalenceHash();
}
}

View File

@ -0,0 +1,41 @@
// 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 Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator
{
public TagHelperPrefixDirectiveChunkGenerator(string prefix)
{
Prefix = prefix;
}
public string Prefix { get; }
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
var other = obj as TagHelperPrefixDirectiveChunkGenerator;
return base.Equals(other) &&
string.Equals(Prefix, other.Prefix, StringComparison.Ordinal);
}
/// <inheritdoc />
public override int GetHashCode()
{
var combiner = HashCodeCombiner.Start();
combiner.Add(base.GetHashCode());
combiner.Add(Prefix, StringComparer.Ordinal);
return combiner.CombinedHash;
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class TemplateBlockChunkGenerator : ParentChunkGenerator
{
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.StartParentChunk<TemplateChunk>(target);
}
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.EndParentChunk();
}
}
}

View File

@ -0,0 +1,252 @@
// 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.Diagnostics;
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal struct TextChange
{
private string _newText;
private string _oldText;
/// <summary>
/// Constructor for changes where the position hasn't moved (primarily for tests)
/// </summary>
internal TextChange(int position, int oldLength, ITextBuffer oldBuffer, int newLength, ITextBuffer newBuffer)
: this(position, oldLength, oldBuffer, position, newLength, newBuffer)
{
}
public TextChange(
int oldPosition,
int oldLength,
ITextBuffer oldBuffer,
int newPosition,
int newLength,
ITextBuffer newBuffer)
: this()
{
if (oldBuffer == null)
{
throw new ArgumentNullException(nameof(oldBuffer));
}
if (newBuffer == null)
{
throw new ArgumentNullException(nameof(newBuffer));
}
if (oldPosition < 0)
{
throw new ArgumentOutOfRangeException(nameof(oldPosition), LegacyResources.FormatArgument_Must_Be_GreaterThanOrEqualTo(0));
}
if (newPosition < 0)
{
throw new ArgumentOutOfRangeException(nameof(newPosition), LegacyResources.FormatArgument_Must_Be_GreaterThanOrEqualTo(0));
}
if (oldLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(oldLength), LegacyResources.FormatArgument_Must_Be_GreaterThanOrEqualTo(0));
}
if (newLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(newLength), LegacyResources.FormatArgument_Must_Be_GreaterThanOrEqualTo(0));
}
OldPosition = oldPosition;
NewPosition = newPosition;
OldLength = oldLength;
NewLength = newLength;
NewBuffer = newBuffer;
OldBuffer = oldBuffer;
}
public int OldPosition { get; }
public int NewPosition { get; }
public int OldLength { get; }
public int NewLength { get; }
public ITextBuffer NewBuffer { get; }
public ITextBuffer OldBuffer { get; }
/// <remark>
/// Note: This property is not thread safe, and will move position on the textbuffer while being read.
/// https://aspnetwebstack.codeplex.com/workitem/1317, tracks making this immutable and improving the access
/// to ITextBuffer to be thread safe.
/// </remark>
public string OldText
{
get
{
if (_oldText == null && OldBuffer != null)
{
_oldText = GetText(OldBuffer, OldPosition, OldLength);
}
return _oldText;
}
}
/// <remark>
/// Note: This property is not thread safe, and will move position on the textbuffer while being read.
/// https://aspnetwebstack.codeplex.com/workitem/1317, tracks making this immutable and improving the access
/// to ITextBuffer to be thread safe.
/// </remark>
public string NewText
{
get
{
if (_newText == null)
{
_newText = GetText(NewBuffer, NewPosition, NewLength);
}
return _newText;
}
}
public bool IsInsert
{
get { return OldLength == 0 && NewLength > 0; }
}
public bool IsDelete
{
get { return OldLength > 0 && NewLength == 0; }
}
public bool IsReplace
{
get { return OldLength > 0 && NewLength > 0; }
}
public override bool Equals(object obj)
{
if (!(obj is TextChange))
{
return false;
}
var change = (TextChange)obj;
return change.OldPosition == OldPosition &&
change.NewPosition == NewPosition &&
change.OldLength == OldLength &&
change.NewLength == NewLength &&
OldBuffer.Equals(change.OldBuffer) &&
NewBuffer.Equals(change.NewBuffer);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(OldPosition);
hashCodeCombiner.Add(NewPosition);
hashCodeCombiner.Add(OldLength);
hashCodeCombiner.Add(NewLength);
hashCodeCombiner.Add(OldBuffer);
hashCodeCombiner.Add(NewBuffer);
return hashCodeCombiner;
}
public string ApplyChange(string content, int changeOffset)
{
var changeRelativePosition = OldPosition - changeOffset;
Debug.Assert(changeRelativePosition >= 0);
return content.Remove(changeRelativePosition, OldLength)
.Insert(changeRelativePosition, NewText);
}
/// <summary>
/// Applies the text change to the content of the span and returns the new content.
/// This method doesn't update the span content.
/// </summary>
public string ApplyChange(Span span)
{
return ApplyChange(span.Content, span.Start.AbsoluteIndex);
}
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "({0}:{1}) \"{3}\" -> ({0}:{2}) \"{4}\"", OldPosition, OldLength, NewLength, OldText, NewText);
}
/// <summary>
/// Removes a common prefix from the edit to turn IntelliSense replacements into insertions where possible
/// </summary>
/// <returns>A normalized text change</returns>
public TextChange Normalize()
{
if (OldBuffer != null && IsReplace && NewLength > OldLength && NewText.StartsWith(OldText, StringComparison.Ordinal) && NewPosition == OldPosition)
{
// Normalize the change into an insertion of the uncommon suffix (i.e. strip out the common prefix)
return new TextChange(oldPosition: OldPosition + OldLength,
oldLength: 0,
oldBuffer: OldBuffer,
newPosition: OldPosition + OldLength,
newLength: NewLength - OldLength,
newBuffer: NewBuffer);
}
return this;
}
private static string GetText(ITextBuffer buffer, int position, int length)
{
// Optimization for the common case of one char inserts, in this case we don't even need to seek the buffer.
if (length == 0)
{
return string.Empty;
}
var oldPosition = buffer.Position;
try
{
buffer.Position = position;
// Optimization for the common case of one char inserts, in this case we seek the buffer.
if (length == 1)
{
return ((char)buffer.Read()).ToString();
}
else
{
var builder = new StringBuilder();
for (int i = 0; i < length; i++)
{
var c = (char)buffer.Read();
builder.Append(c);
// This check is probably not necessary, will revisit when fixing https://aspnetwebstack.codeplex.com/workitem/1317
if (Char.IsHighSurrogate(c))
{
builder.Append((char)buffer.Read());
}
}
return builder.ToString();
}
}
finally
{
buffer.Position = oldPosition;
}
}
public static bool operator ==(TextChange left, TextChange right)
{
return left.Equals(right);
}
public static bool operator !=(TextChange left, TextChange right)
{
return !left.Equals(right);
}
}
}

View File

@ -0,0 +1,167 @@
// 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.IO;
using System.Linq;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal static class TextReaderExtensions
{
public static string ReadUntil(this TextReader reader, char terminator)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
return ReadUntil(reader, terminator, inclusive: false);
}
public static string ReadUntil(this TextReader reader, char terminator, bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
// Rather not allocate an array to use ReadUntil(TextReader, params char[]) so we'll just call the predicate version directly
return ReadUntil(reader, c => c == terminator, inclusive);
}
public static string ReadUntil(this TextReader reader, params char[] terminators)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (terminators == null)
{
throw new ArgumentNullException(nameof(terminators));
}
// NOTE: Using named parameters would be difficult here, hence the inline comment
return ReadUntil(reader, inclusive: false, terminators: terminators);
}
public static string ReadUntil(
this TextReader reader,
bool inclusive,
params char[] terminators)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (terminators == null)
{
throw new ArgumentNullException(nameof(terminators));
}
return ReadUntil(reader, c => terminators.Any(tc => tc == c), inclusive: inclusive);
}
public static string ReadUntil(this TextReader reader, Predicate<char> condition)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
return ReadUntil(reader, condition, inclusive: false);
}
public static string ReadUntil(
this TextReader reader,
Predicate<char> condition,
bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
var builder = new StringBuilder();
var ch = -1;
while ((ch = reader.Peek()) != -1 && !condition((char)ch))
{
reader.Read(); // Advance the reader
builder.Append((char)ch);
}
if (inclusive && reader.Peek() != -1)
{
builder.Append((char)reader.Read());
}
return builder.ToString();
}
public static string ReadWhile(this TextReader reader, Predicate<char> condition)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
return ReadWhile(reader, condition, inclusive: false);
}
public static string ReadWhile(
this TextReader reader,
Predicate<char> condition,
bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
return ReadUntil(reader, ch => !condition(ch), inclusive);
}
public static string ReadWhiteSpace(this TextReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
return ReadWhile(reader, c => Char.IsWhiteSpace(c));
}
public static string ReadUntilWhiteSpace(this TextReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
return ReadUntil(reader, c => Char.IsWhiteSpace(c));
}
}
}

View File

@ -0,0 +1,440 @@
// 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.Text;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract partial class Tokenizer<TSymbol, TSymbolType> : ITokenizer
where TSymbolType : struct
where TSymbol : SymbolBase<TSymbolType>
{
protected Tokenizer(ITextDocument source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
Source = source;
Buffer = new StringBuilder();
CurrentErrors = new List<RazorError>();
StartSymbol();
}
protected List<RazorError> CurrentErrors { get; }
protected abstract int StartState { get; }
protected int? CurrentState { get; set; }
protected TSymbol CurrentSymbol { get; private set; }
public ITextDocument Source { get; private set; }
protected StringBuilder Buffer { get; private set; }
protected bool EndOfFile
{
get { return Source.Peek() == -1; }
}
public abstract TSymbolType RazorCommentStarType { get; }
public abstract TSymbolType RazorCommentType { get; }
public abstract TSymbolType RazorCommentTransitionType { get; }
protected bool HaveContent
{
get { return Buffer.Length > 0; }
}
protected char CurrentCharacter
{
get
{
var peek = Source.Peek();
return peek == -1 ? '\0' : (char)peek;
}
}
protected SourceLocation CurrentLocation
{
get { return Source.Location; }
}
protected SourceLocation CurrentStart { get; private set; }
protected abstract TSymbol CreateSymbol(SourceLocation start, string content, TSymbolType type, IReadOnlyList<RazorError> errors);
protected abstract StateResult Dispatch();
ISymbol ITokenizer.NextSymbol()
{
return NextSymbol();
}
public virtual TSymbol NextSymbol()
{
// Post-Condition: Buffer should be empty at the start of Next()
Debug.Assert(Buffer.Length == 0);
StartSymbol();
if (EndOfFile)
{
return null;
}
var symbol = Turn();
// Post-Condition: Buffer should be empty at the end of Next()
Debug.Assert(Buffer.Length == 0);
return symbol;
}
protected virtual TSymbol Turn()
{
if (CurrentState != null)
{
// Run until we get into the stop state or have a result.
do
{
var next = Dispatch();
CurrentState = next.State;
CurrentSymbol = next.Result;
}
while (CurrentState != null && CurrentSymbol == null);
if (CurrentState == null)
{
return default(TSymbol); // Terminated
}
return CurrentSymbol;
}
return default(TSymbol);
}
public void Reset()
{
CurrentState = StartState;
}
/// <summary>
/// Returns a result indicating that the machine should stop executing and return null output.
/// </summary>
protected StateResult Stop()
{
return default(StateResult);
}
/// <summary>
/// Returns a result indicating that this state has no output and the machine should immediately invoke the specified state
/// </summary>
/// <remarks>
/// By returning no output, the state machine will invoke the next state immediately, before returning
/// controller to the caller of <see cref="Turn"/>
/// </remarks>
protected StateResult Transition(int state)
{
return new StateResult(state, result: null);
}
/// <summary>
/// Returns a result containing the specified output and indicating that the next call to
/// <see cref="Turn"/> should invoke the provided state.
/// </summary>
protected StateResult Transition(int state, TSymbol result)
{
return new StateResult(state, result);
}
protected StateResult Transition(RazorCommentTokenizerState state)
{
return new StateResult((int)state, result: null);
}
protected StateResult Transition(RazorCommentTokenizerState state, TSymbol result)
{
return new StateResult((int)state, result);
}
/// <summary>
/// Returns a result indicating that this state has no output and the machine should remain in this state
/// </summary>
/// <remarks>
/// By returning no output, the state machine will re-invoke the current state again before returning
/// controller to the caller of <see cref="Turn"/>
/// </remarks>
protected StateResult Stay()
{
return new StateResult(CurrentState, result: null);
}
/// <summary>
/// Returns a result containing the specified output and indicating that the next call to
/// <see cref="Turn"/> should re-invoke the current state.
/// </summary>
protected StateResult Stay(TSymbol result)
{
return new StateResult(CurrentState, result);
}
protected TSymbol Single(TSymbolType type)
{
TakeCurrent();
return EndSymbol(type);
}
protected void StartSymbol()
{
Buffer.Clear();
CurrentStart = CurrentLocation;
CurrentErrors.Clear();
}
protected TSymbol EndSymbol(TSymbolType type)
{
return EndSymbol(CurrentStart, type);
}
protected TSymbol EndSymbol(SourceLocation start, TSymbolType type)
{
TSymbol sym = null;
if (HaveContent)
{
// Perf: Don't allocate a new errors array unless necessary.
var errors = CurrentErrors.Count == 0 ? RazorError.EmptyArray : new RazorError[CurrentErrors.Count];
for (var i = 0; i < CurrentErrors.Count; i++)
{
errors[i] = CurrentErrors[i];
}
sym = CreateSymbol(start, Buffer.ToString(), type, errors);
}
StartSymbol();
return sym;
}
protected bool TakeUntil(Func<char, bool> predicate)
{
// Take all the characters up to the end character
while (!EndOfFile && !predicate(CurrentCharacter))
{
TakeCurrent();
}
// Why did we end?
return !EndOfFile;
}
protected void TakeCurrent()
{
if (EndOfFile)
{
return;
} // No-op
Buffer.Append(CurrentCharacter);
MoveNext();
}
protected void MoveNext()
{
Source.Read();
}
protected bool TakeAll(string expected, bool caseSensitive)
{
return Lookahead(expected, takeIfMatch: true, caseSensitive: caseSensitive);
}
protected char Peek()
{
using (var lookahead = BeginLookahead(Source))
{
MoveNext();
return CurrentCharacter;
}
}
protected StateResult AfterRazorCommentTransition()
{
if (CurrentCharacter != '*')
{
// We've been moved since last time we were asked for a symbol... reset the state
return Transition(StartState);
}
AssertCurrent('*');
TakeCurrent();
return Transition(1002, EndSymbol(RazorCommentStarType));
}
protected StateResult RazorCommentBody()
{
TakeUntil(c => c == '*');
if (CurrentCharacter == '*')
{
if (Peek() == '@')
{
if (HaveContent)
{
return Transition(
RazorCommentTokenizerState.StarAfterRazorCommentBody,
EndSymbol(RazorCommentType));
}
else
{
return Transition(RazorCommentTokenizerState.StarAfterRazorCommentBody);
}
}
else
{
TakeCurrent();
return Stay();
}
}
return Transition(StartState, EndSymbol(RazorCommentType));
}
protected StateResult StarAfterRazorCommentBody()
{
AssertCurrent('*');
TakeCurrent();
return Transition(
RazorCommentTokenizerState.AtSymbolAfterRazorCommentBody,
EndSymbol(RazorCommentStarType));
}
protected StateResult AtSymbolAfterRazorCommentBody()
{
AssertCurrent('@');
TakeCurrent();
return Transition(StartState, EndSymbol(RazorCommentTransitionType));
}
/// <summary>
/// Internal for unit testing
/// </summary>
internal bool Lookahead(string expected, bool takeIfMatch, bool caseSensitive)
{
Func<char, char> filter = c => c;
if (!caseSensitive)
{
filter = char.ToLowerInvariant;
}
if (expected.Length == 0 || filter(CurrentCharacter) != filter(expected[0]))
{
return false;
}
// Capture the current buffer content in case we have to backtrack
string oldBuffer = null;
if (takeIfMatch)
{
oldBuffer = Buffer.ToString();
}
using (var lookahead = BeginLookahead(Source))
{
for (int i = 0; i < expected.Length; i++)
{
if (filter(CurrentCharacter) != filter(expected[i]))
{
if (takeIfMatch)
{
// Clear the buffer and put the old buffer text back
Buffer.Clear();
Buffer.Append(oldBuffer);
}
// Return without accepting lookahead (thus rejecting it)
return false;
}
if (takeIfMatch)
{
TakeCurrent();
}
else
{
MoveNext();
}
}
if (takeIfMatch)
{
lookahead.Accept();
}
}
return true;
}
[Conditional("DEBUG")]
internal void AssertCurrent(char current)
{
Debug.Assert(CurrentCharacter == current, "CurrentCharacter Assumption violated", "Assumed that the current character would be {0}, but it is actually {1}", current, CurrentCharacter);
}
protected enum RazorCommentTokenizerState
{
AfterRazorCommentTransition = 1000,
EscapedRazorCommentTransition,
RazorCommentBody,
StarAfterRazorCommentBody,
AtSymbolAfterRazorCommentBody,
}
protected struct StateResult
{
public StateResult(int? state, TSymbol result)
{
State = state;
Result = result;
}
public int? State { get; }
public TSymbol Result { get; }
}
private static LookaheadToken BeginLookahead(ITextBuffer buffer)
{
var start = buffer.Position;
return new LookaheadToken(buffer);
}
private struct LookaheadToken : IDisposable
{
private readonly ITextBuffer _buffer;
private readonly int _position;
private bool _accepted;
public LookaheadToken(ITextBuffer buffer)
{
_buffer = buffer;
_position = buffer.Position;
_accepted = false;
}
public void Accept()
{
_accepted = true;
}
public void Dispose()
{
if (!_accepted)
{
_buffer.Position = _position;
}
}
}
}
}

View File

@ -0,0 +1,661 @@
// 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.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal abstract partial class TokenizerBackedParser<TTokenizer, TSymbol, TSymbolType> : ParserBase
where TSymbolType : struct
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
private readonly TokenizerView<TTokenizer, TSymbol, TSymbolType> _tokenizer;
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer, TSymbol, TSymbolType> language, ParserContext context)
: base(context)
{
Span = new SpanBuilder();
Language = language;
var languageTokenizer = Language.CreateTokenizer(Context.Source);
_tokenizer = new TokenizerView<TTokenizer, TSymbol, TSymbolType>(languageTokenizer);
}
protected SpanBuilder Span { get; private set; }
protected Action<SpanBuilder> SpanConfig { get; set; }
protected TSymbol CurrentSymbol
{
get { return _tokenizer.Current; }
}
protected TSymbol PreviousSymbol { get; private set; }
protected SourceLocation CurrentLocation
{
get { return (EndOfFile || CurrentSymbol == null) ? Context.Source.Location : CurrentSymbol.Start; }
}
protected bool EndOfFile
{
get { return _tokenizer.EndOfFile; }
}
protected LanguageCharacteristics<TTokenizer, TSymbol, TSymbolType> Language { get; }
protected virtual void HandleEmbeddedTransition()
{
}
protected virtual bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
{
return false;
}
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
{
foreach (ISymbol sym in Language.TokenizeString(start, content))
{
span.Accept(sym);
}
}
protected void Initialize(SpanBuilder span)
{
if (SpanConfig != null)
{
SpanConfig(span);
}
}
protected TSymbol Lookahead(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
else if (count == 0)
{
return CurrentSymbol;
}
// We add 1 in order to store the current symbol.
var symbols = new TSymbol[count + 1];
var currentSymbol = CurrentSymbol;
symbols[0] = currentSymbol;
// We need to look forward "count" many times.
for (var i = 1; i <= count; i++)
{
NextToken();
symbols[i] = CurrentSymbol;
}
// Restore Tokenizer's location to where it was pointing before the look-ahead.
for (var i = count; i >= 0; i--)
{
PutBack(symbols[i]);
}
// The PutBacks above will set CurrentSymbol to null. EnsureCurrent will set our CurrentSymbol to the
// next symbol.
EnsureCurrent();
return symbols[count];
}
protected internal bool NextToken()
{
PreviousSymbol = CurrentSymbol;
return _tokenizer.Next();
}
// Helpers
[Conditional("DEBUG")]
internal void Assert(TSymbolType expectedType)
{
Debug.Assert(!EndOfFile && SymbolTypeEquals(CurrentSymbol.Type, expectedType));
}
abstract protected bool SymbolTypeEquals(TSymbolType x, TSymbolType y);
protected internal void PutBack(TSymbol symbol)
{
if (symbol != null)
{
_tokenizer.PutBack(symbol);
}
}
/// <summary>
/// Put the specified symbols back in the input stream. The provided list MUST be in the ORDER THE SYMBOLS WERE READ. The
/// list WILL be reversed and the Putback(TSymbol) will be called on each item.
/// </summary>
/// <remarks>
/// If a document contains symbols: a, b, c, d, e, f
/// and AcceptWhile or AcceptUntil is used to collect until d
/// the list returned by AcceptWhile/Until will contain: a, b, c IN THAT ORDER
/// that is the correct format for providing to this method. The caller of this method would,
/// in that case, want to put c, b and a back into the stream, so "a, b, c" is the CORRECT order
/// </remarks>
protected internal void PutBack(IEnumerable<TSymbol> symbols)
{
foreach (TSymbol symbol in symbols.Reverse())
{
PutBack(symbol);
}
}
protected internal void PutCurrentBack()
{
if (!EndOfFile && CurrentSymbol != null)
{
PutBack(CurrentSymbol);
}
}
protected internal bool Balance(BalancingModes mode)
{
var left = CurrentSymbol.Type;
var right = Language.FlipBracket(left);
var start = CurrentLocation;
AcceptAndMoveNext();
if (EndOfFile && ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure))
{
Context.ErrorSink.OnError(
start,
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF(
Language.GetSample(left),
Language.GetSample(right)),
length: 1 /* { OR } */);
}
return Balance(mode, left, right, start);
}
protected internal bool Balance(BalancingModes mode, TSymbolType left, TSymbolType right, SourceLocation start)
{
var startPosition = CurrentLocation.AbsoluteIndex;
var nesting = 1;
if (!EndOfFile)
{
var syms = new List<TSymbol>();
do
{
if (IsAtEmbeddedTransition(
(mode & BalancingModes.AllowCommentsAndTemplates) == BalancingModes.AllowCommentsAndTemplates,
(mode & BalancingModes.AllowEmbeddedTransitions) == BalancingModes.AllowEmbeddedTransitions))
{
Accept(syms);
syms.Clear();
HandleEmbeddedTransition();
// Reset backtracking since we've already outputted some spans.
startPosition = CurrentLocation.AbsoluteIndex;
}
if (At(left))
{
nesting++;
}
else if (At(right))
{
nesting--;
}
if (nesting > 0)
{
syms.Add(CurrentSymbol);
}
}
while (nesting > 0 && NextToken());
if (nesting > 0)
{
if ((mode & BalancingModes.NoErrorOnFailure) != BalancingModes.NoErrorOnFailure)
{
Context.ErrorSink.OnError(
start,
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF(
Language.GetSample(left),
Language.GetSample(right)),
length: 1 /* { OR } */);
}
if ((mode & BalancingModes.BacktrackOnFailure) == BalancingModes.BacktrackOnFailure)
{
Context.Source.Position = startPosition;
NextToken();
}
else
{
Accept(syms);
}
}
else
{
// Accept all the symbols we saw
Accept(syms);
}
}
return nesting == 0;
}
protected internal bool NextIs(TSymbolType type)
{
return NextIs(sym => sym != null && SymbolTypeEquals(type, sym.Type));
}
protected internal bool NextIs(params TSymbolType[] types)
{
return NextIs(sym => sym != null && types.Any(t => SymbolTypeEquals(t, sym.Type)));
}
protected internal bool NextIs(Func<TSymbol, bool> condition)
{
var cur = CurrentSymbol;
NextToken();
var result = condition(CurrentSymbol);
PutCurrentBack();
PutBack(cur);
EnsureCurrent();
return result;
}
protected internal bool Was(TSymbolType type)
{
return PreviousSymbol != null && SymbolTypeEquals(PreviousSymbol.Type, type);
}
protected internal bool At(TSymbolType type)
{
return !EndOfFile && CurrentSymbol != null && SymbolTypeEquals(CurrentSymbol.Type, type);
}
protected internal bool AcceptAndMoveNext()
{
Accept(CurrentSymbol);
return NextToken();
}
protected TSymbol AcceptSingleWhiteSpaceCharacter()
{
if (Language.IsWhiteSpace(CurrentSymbol))
{
Tuple<TSymbol, TSymbol> pair = Language.SplitSymbol(CurrentSymbol, 1, Language.GetKnownSymbolType(KnownSymbolType.WhiteSpace));
Accept(pair.Item1);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
NextToken();
return pair.Item2;
}
return null;
}
protected internal void Accept(IEnumerable<TSymbol> symbols)
{
foreach (TSymbol symbol in symbols)
{
Accept(symbol);
}
}
protected internal void Accept(TSymbol symbol)
{
if (symbol != null)
{
foreach (var error in symbol.Errors)
{
Context.ErrorSink.OnError(error);
}
Span.Accept(symbol);
}
}
protected internal bool AcceptAll(params TSymbolType[] types)
{
foreach (TSymbolType type in types)
{
if (CurrentSymbol == null || !SymbolTypeEquals(CurrentSymbol.Type, type))
{
return false;
}
AcceptAndMoveNext();
}
return true;
}
protected internal void AddMarkerSymbolIfNecessary()
{
AddMarkerSymbolIfNecessary(CurrentLocation);
}
protected internal void AddMarkerSymbolIfNecessary(SourceLocation location)
{
if (Span.Symbols.Count == 0 && Context.Builder.LastAcceptedCharacters != AcceptedCharacters.Any)
{
Accept(Language.CreateMarkerSymbol(location));
}
}
protected internal void Output(SpanKind kind)
{
Configure(kind, null);
Output();
}
protected internal void Output(SpanKind kind, AcceptedCharacters accepts)
{
Configure(kind, accepts);
Output();
}
protected internal void Output(AcceptedCharacters accepts)
{
Configure(null, accepts);
Output();
}
private void Output()
{
if (Span.Symbols.Count > 0)
{
var builtSpan = Span.Build();
Context.Builder.Add(builtSpan);
Initialize(Span);
}
}
protected IDisposable PushSpanConfig()
{
return PushSpanConfig(newConfig: (Action<SpanBuilder, Action<SpanBuilder>>)null);
}
protected IDisposable PushSpanConfig(Action<SpanBuilder> newConfig)
{
return PushSpanConfig(newConfig == null ? (Action<SpanBuilder, Action<SpanBuilder>>)null : (span, _) => newConfig(span));
}
protected IDisposable PushSpanConfig(Action<SpanBuilder, Action<SpanBuilder>> newConfig)
{
Action<SpanBuilder> old = SpanConfig;
ConfigureSpan(newConfig);
return new DisposableAction(() => SpanConfig = old);
}
protected void ConfigureSpan(Action<SpanBuilder> config)
{
SpanConfig = config;
Initialize(Span);
}
protected void ConfigureSpan(Action<SpanBuilder, Action<SpanBuilder>> config)
{
Action<SpanBuilder> prev = SpanConfig;
if (config == null)
{
SpanConfig = null;
}
else
{
SpanConfig = span => config(span, prev);
}
Initialize(Span);
}
protected internal void Expected(KnownSymbolType type)
{
Expected(Language.GetKnownSymbolType(type));
}
protected internal void Expected(params TSymbolType[] types)
{
Debug.Assert(!EndOfFile && CurrentSymbol != null && types.Contains(CurrentSymbol.Type));
AcceptAndMoveNext();
}
protected internal bool Optional(KnownSymbolType type)
{
return Optional(Language.GetKnownSymbolType(type));
}
protected internal bool Optional(TSymbolType type)
{
if (At(type))
{
AcceptAndMoveNext();
return true;
}
return false;
}
protected internal bool Required(TSymbolType expected, bool errorIfNotFound, Func<string, string> errorBase)
{
var found = At(expected);
if (!found && errorIfNotFound)
{
string error;
if (Language.IsNewLine(CurrentSymbol))
{
error = LegacyResources.ErrorComponent_Newline;
}
else if (Language.IsWhiteSpace(CurrentSymbol))
{
error = LegacyResources.ErrorComponent_Whitespace;
}
else if (EndOfFile)
{
error = LegacyResources.ErrorComponent_EndOfFile;
}
else
{
error = LegacyResources.FormatErrorComponent_Character(CurrentSymbol.Content);
}
int errorLength;
if (CurrentSymbol == null || CurrentSymbol.Content == null)
{
errorLength = 1;
}
else
{
errorLength = Math.Max(CurrentSymbol.Content.Length, 1);
}
Context.ErrorSink.OnError(CurrentLocation, errorBase(error), errorLength);
}
return found;
}
protected bool EnsureCurrent()
{
if (CurrentSymbol == null)
{
return NextToken();
}
return true;
}
protected internal void AcceptWhile(TSymbolType type)
{
AcceptWhile(sym => SymbolTypeEquals(type, sym.Type));
}
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
protected internal void AcceptWhile(TSymbolType type1, TSymbolType type2)
{
AcceptWhile(sym => SymbolTypeEquals(type1, sym.Type) || SymbolTypeEquals(type2, sym.Type));
}
protected internal void AcceptWhile(TSymbolType type1, TSymbolType type2, TSymbolType type3)
{
AcceptWhile(sym => SymbolTypeEquals(type1, sym.Type) || SymbolTypeEquals(type2, sym.Type) || SymbolTypeEquals(type3, sym.Type));
}
protected internal void AcceptWhile(params TSymbolType[] types)
{
AcceptWhile(sym => types.Any(expected => SymbolTypeEquals(expected, sym.Type)));
}
protected internal void AcceptUntil(TSymbolType type)
{
AcceptWhile(sym => !SymbolTypeEquals(type, sym.Type));
}
// We want to avoid array allocations and enumeration where possible, so we use the same technique as string.Format
protected internal void AcceptUntil(TSymbolType type1, TSymbolType type2)
{
AcceptWhile(sym => !SymbolTypeEquals(type1, sym.Type) && !SymbolTypeEquals(type2, sym.Type));
}
protected internal void AcceptUntil(TSymbolType type1, TSymbolType type2, TSymbolType type3)
{
AcceptWhile(sym => !SymbolTypeEquals(type1, sym.Type) && !SymbolTypeEquals(type2, sym.Type) && !SymbolTypeEquals(type3, sym.Type));
}
protected internal void AcceptUntil(params TSymbolType[] types)
{
AcceptWhile(sym => types.All(expected => !SymbolTypeEquals(expected, sym.Type)));
}
protected internal void AcceptWhile(Func<TSymbol, bool> condition)
{
Accept(ReadWhileLazy(condition));
}
protected internal IEnumerable<TSymbol> ReadWhile(Func<TSymbol, bool> condition)
{
return ReadWhileLazy(condition).ToList();
}
protected TSymbol AcceptWhiteSpaceInLines()
{
TSymbol lastWs = null;
while (Language.IsWhiteSpace(CurrentSymbol) || Language.IsNewLine(CurrentSymbol))
{
// Capture the previous whitespace node
if (lastWs != null)
{
Accept(lastWs);
}
if (Language.IsWhiteSpace(CurrentSymbol))
{
lastWs = CurrentSymbol;
}
else if (Language.IsNewLine(CurrentSymbol))
{
// Accept newline and reset last whitespace tracker
Accept(CurrentSymbol);
lastWs = null;
}
_tokenizer.Next();
}
return lastWs;
}
protected bool AtIdentifier(bool allowKeywords)
{
return CurrentSymbol != null &&
(Language.IsIdentifier(CurrentSymbol) ||
(allowKeywords && Language.IsKeyword(CurrentSymbol)));
}
// Don't open this to sub classes because it's lazy but it looks eager.
// You have to advance the Enumerable to read the next characters.
internal IEnumerable<TSymbol> ReadWhileLazy(Func<TSymbol, bool> condition)
{
while (EnsureCurrent() && condition(CurrentSymbol))
{
yield return CurrentSymbol;
NextToken();
}
}
private void Configure(SpanKind? kind, AcceptedCharacters? accepts)
{
if (kind != null)
{
Span.Kind = kind.Value;
}
if (accepts != null)
{
Span.EditHandler.AcceptedCharacters = accepts.Value;
}
}
protected virtual void OutputSpanBeforeRazorComment()
{
throw new InvalidOperationException(LegacyResources.Language_Does_Not_Support_RazorComment);
}
private void CommentSpanConfig(SpanBuilder span)
{
span.ChunkGenerator = SpanChunkGenerator.Null;
span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
}
protected void RazorComment()
{
if (!Language.KnowsSymbolType(KnownSymbolType.CommentStart) ||
!Language.KnowsSymbolType(KnownSymbolType.CommentStar) ||
!Language.KnowsSymbolType(KnownSymbolType.CommentBody))
{
throw new InvalidOperationException(LegacyResources.Language_Does_Not_Support_RazorComment);
}
OutputSpanBeforeRazorComment();
using (PushSpanConfig(CommentSpanConfig))
{
using (Context.Builder.StartBlock(BlockType.Comment))
{
Context.Builder.CurrentBlock.ChunkGenerator = new RazorCommentChunkGenerator();
var start = CurrentLocation;
Expected(KnownSymbolType.CommentStart);
Output(SpanKind.Transition, AcceptedCharacters.None);
Expected(KnownSymbolType.CommentStar);
Output(SpanKind.MetaCode, AcceptedCharacters.None);
Optional(KnownSymbolType.CommentBody);
AddMarkerSymbolIfNecessary();
Output(SpanKind.Comment);
var errorReported = false;
if (!Optional(KnownSymbolType.CommentStar))
{
errorReported = true;
Context.ErrorSink.OnError(
start,
LegacyResources.ParseError_RazorComment_Not_Terminated,
length: 2 /* @* */);
}
else
{
Output(SpanKind.MetaCode, AcceptedCharacters.None);
}
if (!Optional(KnownSymbolType.CommentStart))
{
if (!errorReported)
{
errorReported = true;
Context.ErrorSink.OnError(
start,
LegacyResources.ParseError_RazorComment_Not_Terminated,
length: 2 /* @* */);
}
}
else
{
Output(SpanKind.Transition, AcceptedCharacters.None);
}
}
}
Initialize(Span);
}
}
}

View File

@ -0,0 +1,52 @@
// 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.Diagnostics;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class TokenizerView<TTokenizer, TSymbol, TSymbolType>
where TSymbolType : struct
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
public TokenizerView(TTokenizer tokenizer)
{
Tokenizer = tokenizer;
}
public TTokenizer Tokenizer { get; private set; }
public bool EndOfFile { get; private set; }
public TSymbol Current { get; private set; }
public ITextDocument Source
{
get { return Tokenizer.Source; }
}
public bool Next()
{
Current = Tokenizer.NextSymbol();
EndOfFile = (Current == null);
return !EndOfFile;
}
public void PutBack(TSymbol symbol)
{
Debug.Assert(Source.Position == symbol.Start.AbsoluteIndex + symbol.Content.Length);
if (Source.Position != symbol.Start.AbsoluteIndex + symbol.Content.Length)
{
// We've already passed this symbol
throw new InvalidOperationException(
LegacyResources.FormatTokenizerView_CannotPutBack(
symbol.Start.AbsoluteIndex + symbol.Content.Length,
Source.Position));
}
Source.Position -= symbol.Content.Length;
Current = null;
EndOfFile = Source.Position >= Source.Length;
Tokenizer.Reset();
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class TypeMemberChunkGenerator : SpanChunkGenerator
{
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
//context.ChunkTreeBuilder.AddTypeMemberChunk(target.Content, target);
}
public override string ToString()
{
return "TypeMember";
}
}
}

View File

@ -0,0 +1,351 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Argument_Cannot_Be_Null_Or_Empty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value>
</data>
<data name="Argument_Must_Be_Between" xml:space="preserve">
<value>Value must be between {0} and {1}.</value>
</data>
<data name="Argument_Must_Be_Enum_Member" xml:space="preserve">
<value>Value must be a value from the "{0}" enumeration.</value>
</data>
<data name="Argument_Must_Be_GreaterThan" xml:space="preserve">
<value>Value must be greater than {0}.</value>
</data>
<data name="Argument_Must_Be_GreaterThanOrEqualTo" xml:space="preserve">
<value>Value must be greater than or equal to {0}.</value>
</data>
<data name="Argument_Must_Be_LessThan" xml:space="preserve">
<value>Value must be less than {0}.</value>
</data>
<data name="Argument_Must_Be_LessThanOrEqualTo" xml:space="preserve">
<value>Value must be less than or equal to {0}.</value>
</data>
<data name="Argument_Must_Be_Null_Or_Non_Empty" xml:space="preserve">
<value>Value cannot be an empty string. It must either be null or a non-empty string.</value>
</data>
<data name="BlockName_Code" xml:space="preserve">
<value>code</value>
<comment>This is a literal used when composing ParserError_* messages. Most blocks are named by the keyword that starts them, for example "if". However, for those without keywords, a (localizable) name must be used. This literal is ALWAYS used mid-sentence, thus should not be capitalized.</comment>
</data>
<data name="BlockName_ExplicitExpression" xml:space="preserve">
<value>explicit expression</value>
<comment>This is a literal used when composing ParserError_* messages. Most blocks are named by the keyword that starts them, for example "if". However, for those without keywords, a (localizable) name must be used. This literal is ALWAYS used mid-sentence, thus should not be capitalized.</comment>
</data>
<data name="Block_Type_Not_Specified" xml:space="preserve">
<value>Block cannot be built because a Type has not been specified in the BlockBuilder</value>
</data>
<data name="CSharpSymbol_CharacterLiteral" xml:space="preserve">
<value>&lt;&lt;character literal&gt;&gt;</value>
</data>
<data name="CSharpSymbol_Comment" xml:space="preserve">
<value>&lt;&lt;comment&gt;&gt;</value>
</data>
<data name="CSharpSymbol_Identifier" xml:space="preserve">
<value>&lt;&lt;identifier&gt;&gt;</value>
</data>
<data name="CSharpSymbol_IntegerLiteral" xml:space="preserve">
<value>&lt;&lt;integer literal&gt;&gt;</value>
</data>
<data name="CSharpSymbol_Keyword" xml:space="preserve">
<value>&lt;&lt;keyword&gt;&gt;</value>
</data>
<data name="CSharpSymbol_Newline" xml:space="preserve">
<value>&lt;&lt;newline sequence&gt;&gt;</value>
</data>
<data name="CSharpSymbol_RealLiteral" xml:space="preserve">
<value>&lt;&lt;real literal&gt;&gt;</value>
</data>
<data name="CSharpSymbol_StringLiteral" xml:space="preserve">
<value>&lt;&lt;string literal&gt;&gt;</value>
</data>
<data name="CSharpSymbol_Whitespace" xml:space="preserve">
<value>&lt;&lt;white space&gt;&gt;</value>
</data>
<data name="EndBlock_Called_Without_Matching_StartBlock" xml:space="preserve">
<value>"EndBlock" was called without a matching call to "StartBlock".</value>
</data>
<data name="ErrorComponent_Character" xml:space="preserve">
<value>"{0}" character</value>
</data>
<data name="ErrorComponent_EndOfFile" xml:space="preserve">
<value>end of file</value>
</data>
<data name="ErrorComponent_Newline" xml:space="preserve">
<value>line break</value>
</data>
<data name="ErrorComponent_Whitespace" xml:space="preserve">
<value>space or line break</value>
</data>
<data name="HtmlSymbol_NewLine" xml:space="preserve">
<value>&lt;&lt;newline sequence&gt;&gt;</value>
</data>
<data name="HtmlSymbol_RazorComment" xml:space="preserve">
<value>&lt;&lt;razor comment&gt;&gt;</value>
</data>
<data name="HtmlSymbol_Text" xml:space="preserve">
<value>&lt;&lt;text&gt;&gt;</value>
</data>
<data name="HtmlSymbol_WhiteSpace" xml:space="preserve">
<value>&lt;&lt;white space&gt;&gt;</value>
</data>
<data name="Language_Does_Not_Support_RazorComment" xml:space="preserve">
<value>Cannot use built-in RazorComment handler, language characteristics does not define the CommentStart, CommentStar and CommentBody known symbol types or parser does not override TokenizerBackedParser.OutputSpanBeforeRazorComment</value>
</data>
<data name="ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start" xml:space="preserve">
<value>The "@" character must be followed by a ":", "(", or a C# identifier. If you intended to switch to markup, use an HTML start tag, for example:
@if(isLoggedIn) {
&lt;p&gt;Hello, @user!&lt;/p&gt;
}</value>
</data>
<data name="ParseError_BlockComment_Not_Terminated" xml:space="preserve">
<value>End of file was reached before the end of the block comment. All comments started with "/*" sequence must be terminated with a matching "*/" sequence.</value>
</data>
<data name="ParseError_DirectiveMustHaveValue" xml:space="preserve">
<value>Directive '{0}' must have a value.</value>
</data>
<data name="ParseError_Expected_CloseBracket_Before_EOF" xml:space="preserve">
<value>An opening "{0}" is missing the corresponding closing "{1}".</value>
</data>
<data name="ParseError_Expected_EndOfBlock_Before_EOF" xml:space="preserve">
<value>The {0} block is missing a closing "{1}" character. Make sure you have a matching "{1}" character for all the "{2}" characters within this block, and that none of the "{1}" characters are being interpreted as markup.</value>
</data>
<data name="ParseError_Expected_X" xml:space="preserve">
<value>Expected "{0}".</value>
</data>
<data name="ParseError_HelperDirectiveNotAvailable" xml:space="preserve">
<value>The {0} directive is not supported.</value>
</data>
<data name="ParseError_IncompleteQuotesAroundDirective" xml:space="preserve">
<value>Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.</value>
</data>
<data name="ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName" xml:space="preserve">
<value>The 'inherits' keyword must be followed by a type name on the same line.</value>
</data>
<data name="ParseError_InlineMarkup_Blocks_Cannot_Be_Nested" xml:space="preserve">
<value>Inline markup blocks (@&lt;p&gt;Content&lt;/p&gt;) cannot be nested. Only one level of inline markup is allowed.</value>
</data>
<data name="ParseError_MarkupBlock_Must_Start_With_Tag" xml:space="preserve">
<value>Markup in a code block must start with a tag and all start tags must be matched with end tags. Do not use unclosed tags like "&lt;br&gt;". Instead use self-closing tags like "&lt;br/&gt;".</value>
</data>
<data name="ParseError_MissingEndTag" xml:space="preserve">
<value>The "{0}" element was not closed. All elements must be either self-closing or have a matching end tag.</value>
</data>
<data name="ParseError_MissingOpenBraceAfterSection" xml:space="preserve">
<value>Sections cannot be empty. The "@section" keyword must be followed by a block of markup surrounded by "{}". For example:
@section Sidebar {
&lt;!-- Markup and text goes here --&gt;
}</value>
</data>
<data name="ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock" xml:space="preserve">
<value>Namespace imports and type aliases cannot be placed within code blocks. They must immediately follow an "@" character in markup. It is recommended that you put them at the top of the page, as in the following example:
@using System.Drawing;
@{
// OK here to use types from System.Drawing in the page.
}</value>
</data>
<data name="ParseError_OuterTagMissingName" xml:space="preserve">
<value>Outer tag is missing a name. The first character of a markup block must be an HTML tag with a valid name.</value>
</data>
<data name="ParseError_RazorComment_Not_Terminated" xml:space="preserve">
<value>End of file was reached before the end of the block comment. All comments that start with the "@*" sequence must be terminated with a matching "*@" sequence.</value>
</data>
<data name="ParseError_ReservedWord" xml:space="preserve">
<value>"{0}" is a reserved word and cannot be used in implicit expressions. An explicit expression ("@()") must be used.</value>
</data>
<data name="ParseError_Sections_Cannot_Be_Nested" xml:space="preserve">
<value>Section blocks ("{0}") cannot be nested. Only one level of section blocks are allowed.</value>
</data>
<data name="ParseError_SingleLine_ControlFlowStatements_Not_Allowed" xml:space="preserve">
<value>Expected a "{0}" but found a "{1}". Block statements must be enclosed in "{{" and "}}". You cannot use single-statement control-flow statements in CSHTML pages. For example, the following is not allowed:
@if(isLoggedIn)
&lt;p&gt;Hello, @user&lt;/p&gt;
Instead, wrap the contents of the block in "{{}}":
@if(isLoggedIn) {{
&lt;p&gt;Hello, @user&lt;/p&gt;
}}</value>
<comment>{0} is only ever a single character</comment>
</data>
<data name="ParseError_TextTagCannotContainAttributes" xml:space="preserve">
<value>"&lt;text&gt;" and "&lt;/text&gt;" tags cannot contain attributes.</value>
</data>
<data name="ParseError_UnexpectedEndTag" xml:space="preserve">
<value>Encountered end tag "{0}" with no matching start tag. Are your start/end tags properly balanced?</value>
</data>
<data name="ParseError_Unexpected_Character_At_Section_Name_Start" xml:space="preserve">
<value>Unexpected {0} after section keyword. Section names must start with an "_" or alphabetic character, and the remaining characters must be either "_" or alphanumeric.</value>
</data>
<data name="ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS" xml:space="preserve">
<value>"{0}" is not valid at the start of a code block. Only identifiers, keywords, comments, "(" and "{{" are valid.</value>
<comment>"{{" is an escape sequence for string.Format, when outputted to the user it will be displayed as "{"</comment>
</data>
<data name="ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock" xml:space="preserve">
<value>End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"</value>
</data>
<data name="ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock1" xml:space="preserve">
<value>End-of-file was found after the "@" character. "@" must be followed by a valid code block. If you want to output an "@", escape it using the sequence: "@@"</value>
</data>
<data name="ParseError_Unexpected_Nested_CodeBlock" xml:space="preserve">
<value>Unexpected "{" after "@" character. Once inside the body of a code block (@if {}, @{}, etc.) you do not need to use "@{" to switch to code.</value>
</data>
<data name="ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS" xml:space="preserve">
<value>A space or line break was encountered after the "@" character. Only valid identifiers, keywords, comments, "(" and "{" are valid at the start of a code block and they must occur immediately following "@" with no space in between.</value>
</data>
<data name="ParseError_UnfinishedTag" xml:space="preserve">
<value>End of file or an unexpected character was reached before the "{0}" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing ("&lt;br /&gt;") or have matching end tags ("&lt;p&gt;Hello&lt;/p&gt;"). If you intended to display a "&lt;" character, use the "&amp;lt;" HTML entity.</value>
</data>
<data name="ParseError_Unterminated_String_Literal" xml:space="preserve">
<value>Unterminated string literal. Strings that start with a quotation mark (") must be terminated before the end of the line. However, strings that start with @ and a quotation mark (@") can span multiple lines.</value>
</data>
<data name="ParserContext_CannotCompleteTree_NoRootBlock" xml:space="preserve">
<value>Cannot complete the tree, StartBlock must be called at least once.</value>
</data>
<data name="ParserContext_CannotCompleteTree_OutstandingBlocks" xml:space="preserve">
<value>Cannot complete the tree, there are still open blocks.</value>
</data>
<data name="ParserContext_NoCurrentBlock" xml:space="preserve">
<value>Cannot finish span, there is no current block. Call StartBlock at least once before finishing a span</value>
</data>
<data name="ParserContext_ParseComplete" xml:space="preserve">
<value>Cannot complete action, the parser has finished. Only CompleteParse can be called to extract the final parser results after the parser has finished</value>
</data>
<data name="Parser_Context_Not_Set" xml:space="preserve">
<value>Parser was started with a null Context property. The Context property must be set BEFORE calling any methods on the parser.</value>
</data>
<data name="SectionExample_CS" xml:space="preserve">
<value>@section Header { ... }</value>
<comment>In CSHTML, the @section keyword is case-sensitive and lowercase (as with all C# keywords)</comment>
</data>
<data name="SourceLocationFilePathDoesNotMatch" xml:space="preserve">
<value>Cannot perform '{1}' operations on '{0}' instances with different file paths.</value>
</data>
<data name="Symbol_Unknown" xml:space="preserve">
<value>&lt;&lt;unknown&gt;&gt;</value>
</data>
<data name="TokenizerView_CannotPutBack" xml:space="preserve">
<value>In order to put a symbol back, it must have been the symbol which ended at the current position. The specified symbol ends at {0}, but the current position is {1}</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public abstract class RazorSyntaxTree
{
internal static RazorSyntaxTree Create(Block root, IEnumerable<RazorError> diagnostics)
{
if (root == null)
{
throw new ArgumentNullException(nameof(root));
}
if (diagnostics == null)
{
throw new ArgumentNullException(nameof(diagnostics));
}
return new DefaultRazorSyntaxTree(root, new List<RazorError>(diagnostics));
}
public static RazorSyntaxTree Parse(RazorSourceDocument source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var parser = new RazorParser();
using (var reader = source.CreateReader())
{
return parser.Parse(reader);
}
}
internal abstract IReadOnlyList<RazorError> Diagnostics { get; }
internal abstract Block Root { get; }
}
}

View File

@ -0,0 +1,46 @@
// 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.Diagnostics;
using System.IO;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
public static class BaselineWriter
{
private static object baselineLock = new object();
[Conditional("GENERATE_BASELINES")]
public static void WriteBaseline(string baselineFile, string output)
{
var root = RecursiveFind("Razor.sln", Path.GetFullPath("."));
var baselinePath = Path.Combine(root, baselineFile);
// Serialize writes to minimize contention for file handles and directory access.
lock (baselineLock)
{
// Update baseline
using (var stream = File.Open(baselinePath, FileMode.Create, FileAccess.Write))
{
using (var writer = new StreamWriter(stream))
{
writer.Write(output);
}
}
}
}
private static string RecursiveFind(string path, string start)
{
var test = Path.Combine(start, path);
if (File.Exists(test))
{
return start;
}
else
{
return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
}
}
}
}

View File

@ -0,0 +1,28 @@
// 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.
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal static class BlockExtensions
{
public static void LinkNodes(this Block self)
{
Span first = null;
Span previous = null;
foreach (Span span in self.Flatten())
{
if (first == null)
{
first = span;
}
span.Previous = previous;
if (previous != null)
{
previous.Next = span;
}
previous = span;
}
}
}
}

View File

@ -0,0 +1,58 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class BlockFactory
{
private SpanFactory _factory;
public BlockFactory(SpanFactory factory)
{
_factory = factory;
}
public Block EscapedMarkupTagBlock(string prefix, string suffix)
{
return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any);
}
public Block EscapedMarkupTagBlock(string prefix, string suffix, params SyntaxTreeNode[] children)
{
return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any, children);
}
public Block EscapedMarkupTagBlock(
string prefix,
string suffix,
AcceptedCharacters acceptedCharacters,
params SyntaxTreeNode[] children)
{
var newChildren = new List<SyntaxTreeNode>(
new SyntaxTreeNode[]
{
_factory.Markup(prefix),
_factory.BangEscape(),
_factory.Markup(suffix).Accepts(acceptedCharacters)
});
newChildren.AddRange(children);
return new MarkupTagBlock(newChildren.ToArray());
}
public Block MarkupTagBlock(string content)
{
return MarkupTagBlock(content, AcceptedCharacters.Any);
}
public Block MarkupTagBlock(string content, AcceptedCharacters acceptedCharacters)
{
return new MarkupTagBlock(
_factory.Markup(content).Accepts(acceptedCharacters)
);
}
}
}

View File

@ -0,0 +1,62 @@
// 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.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class BlockTest
{
[Fact]
public void ConstructorWithBlockBuilderSetsParent()
{
// Arrange
var builder = new BlockBuilder() { Type = BlockType.Comment };
var span = new SpanBuilder() { Kind = SpanKind.Code }.Build();
builder.Children.Add(span);
// Act
var block = builder.Build();
// Assert
Assert.Same(block, span.Parent);
}
[Fact]
public void ConstructorTransfersInstanceOfChunkGeneratorFromBlockBuilder()
{
// Arrange
var expected = new ExpressionChunkGenerator();
var builder = new BlockBuilder()
{
Type = BlockType.Helper,
ChunkGenerator = expected
};
// Act
var actual = builder.Build();
// Assert
Assert.Same(expected, actual.ChunkGenerator);
}
[Fact]
public void ConstructorTransfersChildrenFromBlockBuilder()
{
// Arrange
var expected = new SpanBuilder() { Kind = SpanKind.Code }.Build();
var builder = new BlockBuilder()
{
Type = BlockType.Functions
};
builder.Children.Add(expected);
// Act
var block = builder.Build();
// Assert
Assert.Same(expected, block.Children.Single());
}
}
}

View File

@ -0,0 +1,202 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// The product code doesn't need this, but having subclasses for the block types makes tests much cleaner :)
internal class StatementBlock : Block
{
private const BlockType ThisBlockType = BlockType.Statement;
public StatementBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public StatementBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public StatementBlock(params SyntaxTreeNode[] children)
: this(ParentChunkGenerator.Null, children)
{
}
}
internal class DirectiveBlock : Block
{
private const BlockType ThisBlockType = BlockType.Directive;
public DirectiveBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public DirectiveBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public DirectiveBlock(params SyntaxTreeNode[] children)
: this(ParentChunkGenerator.Null, children)
{
}
}
internal class FunctionsBlock : Block
{
private const BlockType ThisBlockType = BlockType.Functions;
public FunctionsBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public FunctionsBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public FunctionsBlock(params SyntaxTreeNode[] children)
: this(ParentChunkGenerator.Null, children)
{
}
}
internal class ExpressionBlock : Block
{
private const BlockType ThisBlockType = BlockType.Expression;
public ExpressionBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public ExpressionBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public ExpressionBlock(params SyntaxTreeNode[] children)
: this(new ExpressionChunkGenerator(), children)
{
}
}
internal class MarkupTagBlock : Block
{
private const BlockType ThisBlockType = BlockType.Tag;
public MarkupTagBlock(params SyntaxTreeNode[] children)
: base(ThisBlockType, children, ParentChunkGenerator.Null)
{
}
}
internal class MarkupBlock : Block
{
private const BlockType ThisBlockType = BlockType.Markup;
public MarkupBlock(
BlockType blockType,
IParentChunkGenerator chunkGenerator,
IReadOnlyList<SyntaxTreeNode> children)
: base(blockType, children, chunkGenerator)
{
}
public MarkupBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: this(ThisBlockType, chunkGenerator, children)
{
}
public MarkupBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public MarkupBlock(params SyntaxTreeNode[] children)
: this(ParentChunkGenerator.Null, children)
{
}
}
internal class SectionBlock : Block
{
private const BlockType ThisBlockType = BlockType.Section;
public SectionBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public SectionBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public SectionBlock(params SyntaxTreeNode[] children)
: this(ParentChunkGenerator.Null, children)
{
}
public SectionBlock(IReadOnlyList<SyntaxTreeNode> children)
: this(ParentChunkGenerator.Null, children)
{
}
}
internal class TemplateBlock : Block
{
private const BlockType ThisBlockType = BlockType.Template;
public TemplateBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public TemplateBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public TemplateBlock(params SyntaxTreeNode[] children)
: this(new TemplateBlockChunkGenerator(), children)
{
}
public TemplateBlock(IReadOnlyList<SyntaxTreeNode> children)
: this(new TemplateBlockChunkGenerator(), children)
{
}
}
internal class CommentBlock : Block
{
private const BlockType ThisBlockType = BlockType.Comment;
public CommentBlock(IParentChunkGenerator chunkGenerator, IReadOnlyList<SyntaxTreeNode> children)
: base(ThisBlockType, children, chunkGenerator)
{
}
public CommentBlock(IParentChunkGenerator chunkGenerator, params SyntaxTreeNode[] children)
: this(chunkGenerator, (IReadOnlyList<SyntaxTreeNode>)children)
{
}
public CommentBlock(params SyntaxTreeNode[] children)
: this(new RazorCommentChunkGenerator(), children)
{
}
public CommentBlock(IReadOnlyList<SyntaxTreeNode> children)
: this(new RazorCommentChunkGenerator(), children)
{
}
}
}

View File

@ -0,0 +1,138 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpAutoCompleteTest : CsHtmlCodeParserTestBase
{
[Fact]
public void FunctionsDirectiveAutoCompleteAtEOF()
{
ParseBlockTest(
"@functions{",
new FunctionsBlock(
Factory.CodeTransition("@")
.Accepts(AcceptedCharacters.None),
Factory.MetaCode("functions{")
.Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsFunctionsBody()
.With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
{
AutoCompleteString = "}"
})),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
new SourceLocation(10, 0, 10),
length: 1));
}
[Fact]
public void SectionDirectiveAutoCompleteAtEOF()
{
ParseBlockTest("@section Header {",
new SectionBlock(new SectionChunkGenerator("Header"),
Factory.CodeTransition(),
Factory.MetaCode("section Header {")
.AutoCompleteWith("}", atEndOfSpan: true)
.Accepts(AcceptedCharacters.Any),
new MarkupBlock()),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(16, 0, 16),
length: 1));
}
[Fact]
public void VerbatimBlockAutoCompleteAtEOF()
{
ParseBlockTest("@{",
new StatementBlock(
Factory.CodeTransition(),
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsStatement()
.With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
LegacyResources.BlockName_Code, "}", "{"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void FunctionsDirectiveAutoCompleteAtStartOfFile()
{
ParseBlockTest("@functions{" + Environment.NewLine
+ "foo",
new FunctionsBlock(
Factory.CodeTransition("@")
.Accepts(AcceptedCharacters.None),
Factory.MetaCode("functions{")
.Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + "foo")
.AsFunctionsBody()
.With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)
{
AutoCompleteString = "}"
})),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
new SourceLocation(10, 0, 10),
length: 1));
}
[Fact]
public void SectionDirectiveAutoCompleteAtStartOfFile()
{
ParseBlockTest("@section Header {" + Environment.NewLine
+ "<p>Foo</p>",
new SectionBlock(new SectionChunkGenerator("Header"),
Factory.CodeTransition(),
Factory.MetaCode("section Header {")
.AutoCompleteWith("}", atEndOfSpan: true)
.Accepts(AcceptedCharacters.Any),
new MarkupBlock(
Factory.Markup(Environment.NewLine),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")))),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(16, 0, 16),
length: 1));
}
[Fact]
public void VerbatimBlockAutoCompleteAtStartOfFile()
{
ParseBlockTest("@{" + Environment.NewLine
+ "<p></p>",
new StatementBlock(
Factory.CodeTransition(),
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine)
.AsStatement()
.With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" }),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))),
Factory.Span(SpanKind.Code, new CSharpSymbol(Factory.LocationTracker.CurrentLocation, string.Empty, CSharpSymbolType.Unknown))
.With(new StatementChunkGenerator())
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
LegacyResources.BlockName_Code, "}", "{"),
new SourceLocation(1, 0, 1),
length: 1));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,426 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpDirectivesTest : CsHtmlCodeParserTestBase
{
[Fact]
public void TagHelperPrefixDirective_NoValueSucceeds()
{
ParseBlockTest("@tagHelperPrefix \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"\"")
.AsTagHelperPrefixDirective(string.Empty)));
}
[Fact]
public void TagHelperPrefixDirective_Succeeds()
{
ParseBlockTest("@tagHelperPrefix Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo")
.AsTagHelperPrefixDirective("Foo")));
}
[Fact]
public void TagHelperPrefixDirective_WithQuotes_Succeeds()
{
ParseBlockTest("@tagHelperPrefix \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo\"")
.AsTagHelperPrefixDirective("Foo")));
}
[Fact]
public void TagHelperPrefixDirective_RequiresValue()
{
ParseBlockTest("@tagHelperPrefix ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsTagHelperPrefixDirective(string.Empty)
.Accepts(AcceptedCharacters.AnyExceptNewline)),
new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(
SyntaxConstants.CSharp.TagHelperPrefixKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15));
}
[Fact]
public void TagHelperPrefixDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@tagHelperPrefix \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo")
.AsTagHelperPrefixDirective("\"Foo")),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.TagHelperPrefixKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
}
[Fact]
public void TagHelperPrefixDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@tagHelperPrefix Foo \"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory
.MetaCode(SyntaxConstants.CSharp.TagHelperPrefixKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo \"")
.AsTagHelperPrefixDirective("Foo \"")),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 23, lineIndex: 0, columnIndex: 23, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.TagHelperPrefixKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 7));
}
[Fact]
public void RemoveTagHelperDirective_NoValue_Succeeds()
{
ParseBlockTest("@removeTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"\"")
.AsRemoveTagHelper(string.Empty)));
}
[Fact]
public void RemoveTagHelperDirective_Succeeds()
{
ParseBlockTest("@removeTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo")
.AsRemoveTagHelper("Foo")));
}
[Fact]
public void RemoveTagHelperDirective_WithQuotes_Succeeds()
{
ParseBlockTest("@removeTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo\"")
.AsRemoveTagHelper("Foo")));
}
[Fact]
public void RemoveTagHelperDirective_SupportsSpaces()
{
ParseBlockTest("@removeTagHelper Foo, Bar ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo, Bar ")
.AsRemoveTagHelper("Foo, Bar")
.Accepts(AcceptedCharacters.AnyExceptNewline)));
}
[Fact]
public void RemoveTagHelperDirective_RequiresValue()
{
ParseBlockTest("@removeTagHelper ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsRemoveTagHelper(string.Empty)
.Accepts(AcceptedCharacters.AnyExceptNewline)),
new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 15));
}
[Fact]
public void RemoveTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@removeTagHelper \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo")
.AsRemoveTagHelper("\"Foo")),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
}
[Fact]
public void RemoveTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@removeTagHelper Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.RemoveTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo\"")
.AsRemoveTagHelper("Foo\"")
.Accepts(AcceptedCharacters.AnyExceptNewline)),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 20, lineIndex: 0, columnIndex: 20, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.RemoveTagHelperKeyword),
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 4));
}
[Fact]
public void AddTagHelperDirective_NoValue_Succeeds()
{
ParseBlockTest("@addTagHelper \"\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"\"")
.AsAddTagHelper(string.Empty)));
}
[Fact]
public void AddTagHelperDirective_Succeeds()
{
ParseBlockTest("@addTagHelper Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo")
.AsAddTagHelper("Foo")));
}
[Fact]
public void AddTagHelperDirective_WithQuotes_Succeeds()
{
ParseBlockTest("@addTagHelper \"Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo\"")
.AsAddTagHelper("Foo")));
}
[Fact]
public void AddTagHelperDirectiveSupportsSpaces()
{
ParseBlockTest("@addTagHelper Foo, Bar ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo, Bar ")
.AsAddTagHelper("Foo, Bar")));
}
[Fact]
public void AddTagHelperDirectiveRequiresValue()
{
ParseBlockTest("@addTagHelper ",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsAddTagHelper(string.Empty)
.Accepts(AcceptedCharacters.AnyExceptNewline)),
new RazorError(
LegacyResources.FormatParseError_DirectiveMustHaveValue(SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 1, lineIndex: 0, columnIndex: 1, length: 12));
}
[Fact]
public void AddTagHelperDirective_StartQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@addTagHelper \"Foo",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("\"Foo")
.AsAddTagHelper("\"Foo")),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4));
}
[Fact]
public void AddTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue()
{
ParseBlockTest("@addTagHelper Foo\"",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("Foo\"")
.AsAddTagHelper("Foo\"")
.Accepts(AcceptedCharacters.AnyExceptNewline)),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
absoluteIndex: 17, lineIndex: 0, columnIndex: 17, length: 1),
new RazorError(
LegacyResources.FormatParseError_IncompleteQuotesAroundDirective(
SyntaxConstants.CSharp.AddTagHelperKeyword),
absoluteIndex: 14, lineIndex: 0, columnIndex: 14, length: 4));
}
[Fact]
public void InheritsDirective()
{
ParseBlockTest("@inherits System.Web.WebPages.WebPage",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("System.Web.WebPages.WebPage")
.AsBaseType("System.Web.WebPages.WebPage")));
}
[Fact]
public void InheritsDirectiveSupportsArrays()
{
ParseBlockTest("@inherits string[[]][]",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("string[[]][]")
.AsBaseType("string[[]][]")));
}
[Fact]
public void InheritsDirectiveSupportsNestedGenerics()
{
ParseBlockTest("@inherits System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>")
.AsBaseType("System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication2.Models.RegisterModel>>")));
}
[Fact]
public void InheritsDirectiveSupportsTypeKeywords()
{
ParseBlockTest("@inherits string",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("string")
.AsBaseType("string")));
}
[Fact]
public void InheritsDirectiveSupportsVSTemplateTokens()
{
ParseBlockTest("@inherits $rootnamespace$.MyBase",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.InheritsKeyword + " ")
.Accepts(AcceptedCharacters.None),
Factory.Code("$rootnamespace$.MyBase")
.AsBaseType("$rootnamespace$.MyBase")));
}
[Fact]
public void FunctionsDirective()
{
ParseBlockTest("@functions { foo(); bar(); }",
new FunctionsBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
.Accepts(AcceptedCharacters.None),
Factory.Code(" foo(); bar(); ")
.AsFunctionsBody()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}")
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void EmptyFunctionsDirective()
{
ParseBlockTest("@functions { }",
new FunctionsBlock(
Factory.CodeTransition(),
Factory.MetaCode(SyntaxConstants.CSharp.FunctionsKeyword + " {")
.Accepts(AcceptedCharacters.None),
Factory.Code(" ")
.AsFunctionsBody()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}")
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void SectionDirective()
{
ParseBlockTest("@section Header { <p>F{o}o</p> }",
new SectionBlock(new SectionChunkGenerator("Header"),
Factory.CodeTransition(),
Factory.MetaCode("section Header {")
.AutoCompleteWith(null, atEndOfSpan: true)
.Accepts(AcceptedCharacters.Any),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("F", "{", "o", "}", "o"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" ")),
Factory.MetaCode("}")
.Accepts(AcceptedCharacters.None)));
}
}
}

View File

@ -0,0 +1,695 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpErrorTest : CsHtmlCodeParserTestBase
{
[Fact]
public void ParseBlockHandlesQuotesAfterTransition()
{
ParseBlockTest("@\"",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(KeywordSet)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS('"'),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockWithHelperDirectiveProducesError()
{
ParseBlockTest("@helper fooHelper { }",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("helper")
.AsImplicitExpression(KeywordSet)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.FormatParseError_HelperDirectiveNotAvailable(SyntaxConstants.CSharp.HelperKeyword),
new SourceLocation(1, 0, 1),
length: 6));
}
[Fact]
public void ParseBlockCapturesWhitespaceToEndOfLineInInvalidUsingStatementAndTreatsAsFileCode()
{
ParseBlockTest("using " + Environment.NewLine
+ Environment.NewLine,
new StatementBlock(
Factory.Code("using " + Environment.NewLine).AsStatement()
));
}
[Fact]
public void ParseBlockMethodOutputsOpenCurlyAsCodeSpanIfEofFoundAfterOpenCurlyBrace()
{
ParseBlockTest("{",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsStatement()
.With(new AutoCompleteEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString) { AutoCompleteString = "}" })
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_Code, "}", "{"),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockMethodOutputsZeroLengthCodeSpanIfStatementBlockEmpty()
{
ParseBlockTest("{}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockMethodProducesErrorIfNewlineFollowsTransition()
{
ParseBlockTest("@" + Environment.NewLine,
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
new SourceLocation(1, 0, 1),
Environment.NewLine.Length));
}
[Fact]
public void ParseBlockMethodProducesErrorIfWhitespaceBetweenTransitionAndBlockStartInEmbeddedExpression()
{
ParseBlockTest("{" + Environment.NewLine
+ " @ {}" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Code(" {}" + Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
),
new RazorError(
LegacyResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS,
new SourceLocation(6 + Environment.NewLine.Length, 1, 5),
length: 3));
}
[Fact]
public void ParseBlockMethodProducesErrorIfEOFAfterTransitionInEmbeddedExpression()
{
ParseBlockTest("{" + Environment.NewLine
+ " @",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith("}"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.EmptyCSharp().AsStatement()
),
new RazorError(
LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
6 + Environment.NewLine.Length, 1, 5, length: 1),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_Code, "}", "{"),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockMethodParsesNothingIfFirstCharacterIsNotIdentifierStartOrParenOrBrace()
{
ParseBlockTest("@!!!",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("!"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockShouldReportErrorAndTerminateAtEOFIfIfParenInExplicitExpressionUnclosed()
{
ParseBlockTest("(foo bar" + Environment.NewLine
+ "baz",
new ExpressionBlock(
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code($"foo bar{Environment.NewLine}baz").AsExpression()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_ExplicitExpression, ')', '('),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfIfParenInExplicitExpressionUnclosed()
{
ParseBlockTest("(foo bar" + Environment.NewLine
+ "<html>" + Environment.NewLine
+ "baz" + Environment.NewLine
+ "</html",
new ExpressionBlock(
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code($"foo bar{Environment.NewLine}").AsExpression()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_ExplicitExpression, ')', '('),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockCorrectlyHandlesInCorrectTransitionsIfImplicitExpressionParensUnclosed()
{
ParseBlockTest("Href(" + Environment.NewLine
+ "<h1>@Html.Foo(Bar);</h1>" + Environment.NewLine,
new ExpressionBlock(
Factory.Code("Href(" + Environment.NewLine)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(4, 0, 4),
length: 1));
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ParseBlockShouldReportErrorAndTerminateAtEOFIfParenInImplicitExpressionUnclosed()
{
ParseBlockTest("Foo(Bar(Baz)" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "Boz",
new ExpressionBlock(
Factory.Code($"Foo(Bar(Baz){Environment.NewLine}Biz{Environment.NewLine}Boz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(3, 0, 3),
length: 1));
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfParenInImplicitExpressionUnclosed()
{
ParseBlockTest("Foo(Bar(Baz)" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "<html>" + Environment.NewLine
+ "Boz" + Environment.NewLine
+ "</html>",
new ExpressionBlock(
Factory.Code($"Foo(Bar(Baz){Environment.NewLine}Biz{Environment.NewLine}")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(3, 0, 3),
length: 1));
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ParseBlockShouldReportErrorAndTerminateAtEOFIfBracketInImplicitExpressionUnclosed()
{
ParseBlockTest("Foo[Bar[Baz]" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "Boz",
new ExpressionBlock(
Factory.Code($"Foo[Bar[Baz]{Environment.NewLine}Biz{Environment.NewLine}Boz")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("[", "]"),
new SourceLocation(3, 0, 3),
length: 1));
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ParseBlockShouldReportErrorAndTerminateAtMarkupIfBracketInImplicitExpressionUnclosed()
{
ParseBlockTest("Foo[Bar[Baz]" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "<b>" + Environment.NewLine
+ "Boz" + Environment.NewLine
+ "</b>",
new ExpressionBlock(
Factory.Code($"Foo[Bar[Baz]{Environment.NewLine}Biz{Environment.NewLine}")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("[", "]"),
new SourceLocation(3, 0, 3),
length: 1));
}
// Simple EOF handling errors:
[Fact]
public void ParseBlockReportsErrorIfExplicitCodeBlockUnterminatedAtEOF()
{
ParseBlockTest("{ var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; if(foo != null) { bar(); } ")
.AsStatement()
.AutoCompleteWith("}")),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
LegacyResources.BlockName_Code, '}', '{'),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfClassBlockUnterminatedAtEOF()
{
ParseBlockTest("functions { var foo = bar; if(foo != null) { bar(); } ",
new FunctionsBlock(
Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; if(foo != null) { bar(); } ")
.AsFunctionsBody()
.AutoCompleteWith("}")),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", '}', '{'),
new SourceLocation(10, 0, 10),
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfIfBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("if");
}
[Fact]
public void ParseBlockReportsErrorIfElseBlockUnterminatedAtEOF()
{
ParseBlockTest("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("else", '}', '{'),
new SourceLocation(19, 0, 19),
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfElseIfBlockUnterminatedAtEOF()
{
ParseBlockTest("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("else if", '}', '{'),
new SourceLocation(19, 0, 19),
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfDoBlockUnterminatedAtEOF()
{
ParseBlockTest("do { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("do { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("do", '}', '{'),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfTryBlockUnterminatedAtEOF()
{
ParseBlockTest("try { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("try { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("try", '}', '{'),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfCatchBlockUnterminatedAtEOF()
{
ParseBlockTest("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("catch", '}', '{'),
new SourceLocation(15, 0, 15),
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfFinallyBlockUnterminatedAtEOF()
{
ParseBlockTest("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ",
new StatementBlock(
Factory.Code("try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("finally", '}', '{'),
new SourceLocation(15, 0, 15),
length: 1));
}
[Fact]
public void ParseBlockReportsErrorIfForBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("for");
}
[Fact]
public void ParseBlockReportsErrorIfForeachBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("foreach");
}
[Fact]
public void ParseBlockReportsErrorIfWhileBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("while");
}
[Fact]
public void ParseBlockReportsErrorIfSwitchBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("switch");
}
[Fact]
public void ParseBlockReportsErrorIfLockBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("lock");
}
[Fact]
public void ParseBlockReportsErrorIfUsingBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("using");
}
[Fact]
public void ParseBlockRequiresControlFlowStatementsToHaveBraces()
{
var expectedMessage = LegacyResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed("{", "<");
ParseBlockTest("if(foo) <p>Bar</p> else if(bar) <p>Baz</p> else <p>Boz</p>",
new StatementBlock(
Factory.Code("if(foo) ").AsStatement(),
new MarkupBlock(
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Bar"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
Factory.Code("else if(bar) ").AsStatement(),
new MarkupBlock(
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Baz"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
Factory.Code("else ").AsStatement(),
new MarkupBlock(
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Boz"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None)),
Factory.EmptyCSharp().AsStatement()
),
new RazorError(expectedMessage, 8, 0, 8, 1),
new RazorError(expectedMessage, 32, 0, 32, 1),
new RazorError(expectedMessage, 48, 0, 48, 1));
}
[Fact]
public void ParseBlockIncludesUnexpectedCharacterInSingleStatementControlFlowStatementError()
{
ParseBlockTest("if(foo)) { var bar = foo; }",
new StatementBlock(
Factory.Code("if(foo)) { var bar = foo; }").AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_SingleLine_ControlFlowStatements_Not_Allowed("{", ")"),
new SourceLocation(7, 0, 7),
length: 1));
}
[Fact]
public void ParseBlockOutputsErrorIfAtSignFollowedByLessThanSignAtStatementStart()
{
ParseBlockTest("if(foo) { @<p>Bar</p> }",
new StatementBlock(
Factory.Code("if(foo) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Bar"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
Factory.Code("}").AsStatement()
),
new RazorError(
LegacyResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
new SourceLocation(10, 0, 10),
length: 1));
}
[Fact]
public void ParseBlockTerminatesIfBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseBlockTest("if(foo bar" + Environment.NewLine
+ "baz",
new StatementBlock(
Factory.Code("if(foo bar" + Environment.NewLine).AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(2, 0, 2),
length: 1));
}
[Fact]
public void ParseBlockTerminatesForeachBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseBlockTest("foreach(foo bar" + Environment.NewLine
+ "baz",
new StatementBlock(
Factory.Code("foreach(foo bar" + Environment.NewLine).AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(7, 0, 7),
length: 1));
}
[Fact]
public void ParseBlockTerminatesWhileClauseInDoStatementAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseBlockTest("do { } while(foo bar" + Environment.NewLine
+ "baz",
new StatementBlock(
Factory.Code("do { } while(foo bar" + Environment.NewLine).AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(12, 0, 12),
length: 1));
}
[Fact]
public void ParseBlockTerminatesUsingBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseBlockTest("using(foo bar" + Environment.NewLine
+ "baz",
new StatementBlock(
Factory.Code("using(foo bar" + Environment.NewLine).AsStatement()
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(5, 0, 5),
length: 1));
}
[Fact]
public void ParseBlockResumesIfStatementAfterOpenParen()
{
ParseBlockTest("if(" + Environment.NewLine
+ "else { <p>Foo</p> }",
new StatementBlock(
Factory.Code($"if({Environment.NewLine}else {{").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)),
Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(2, 0, 2),
length: 1));
}
[Fact]
public void ParseBlockTerminatesNormalCSharpStringsAtEOLIfEndQuoteMissing()
{
SingleSpanBlockTest("if(foo) {" + Environment.NewLine
+ " var p = \"foo bar baz" + Environment.NewLine
+ ";" + Environment.NewLine
+ "}",
BlockType.Statement, SpanKind.Code,
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
new SourceLocation(21 + Environment.NewLine.Length, 1, 12),
length: 1));
}
[Fact]
public void ParseBlockTerminatesNormalStringAtEndOfFile()
{
SingleSpanBlockTest("if(foo) { var foo = \"blah blah blah blah blah", BlockType.Statement, SpanKind.Code,
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
new SourceLocation(20, 0, 20),
length: 1),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("if", '}', '{'),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockTerminatesVerbatimStringAtEndOfFile()
{
SingleSpanBlockTest("if(foo) { var foo = @\"blah " + Environment.NewLine
+ "blah; " + Environment.NewLine
+ "<p>Foo</p>" + Environment.NewLine
+ "blah " + Environment.NewLine
+ "blah",
BlockType.Statement, SpanKind.Code,
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
new SourceLocation(20, 0, 20),
length: 1),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("if", '}', '{'),
SourceLocation.Zero,
length: 1));
}
[Fact]
public void ParseBlockCorrectlyParsesMarkupIncorrectyAssumedToBeWithinAStatement()
{
ParseBlockTest("if(foo) {" + Environment.NewLine
+ " var foo = \"foo bar baz" + Environment.NewLine
+ " <p>Foo is @foo</p>" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code($"if(foo) {{{Environment.NewLine} var foo = \"foo bar baz{Environment.NewLine} ").AsStatement(),
new MarkupBlock(
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Foo is "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
Factory.Code("}").AsStatement()
),
new RazorError(
LegacyResources.ParseError_Unterminated_String_Literal,
new SourceLocation(23 + Environment.NewLine.Length, 1, 14),
length: 1));
}
[Fact]
public void ParseBlockCorrectlyParsesAtSignInDelimitedBlock()
{
ParseBlockTest("(Request[\"description\"] ?? @photo.Description)",
new ExpressionBlock(
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("Request[\"description\"] ?? @photo.Description").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockCorrectlyRecoversFromMissingCloseParenInExpressionWithinCode()
{
ParseBlockTest(@"{string.Format(<html></html>}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code("string.Format(")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
BlockFactory.MarkupTagBlock("<html>", AcceptedCharacters.None),
BlockFactory.MarkupTagBlock("</html>", AcceptedCharacters.None)),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
expectedErrors: new[]
{
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(14, 0, 14),
length: 1)
});
}
private void RunUnterminatedSimpleKeywordBlock(string keyword)
{
SingleSpanBlockTest(
keyword + " (foo) { var foo = bar; if(foo != null) { bar(); } ",
BlockType.Statement,
SpanKind.Code,
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(keyword, '}', '{'),
SourceLocation.Zero,
length: 1));
}
}
}

View File

@ -0,0 +1,139 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpExplicitExpressionTest : CsHtmlCodeParserTestBase
{
[Fact]
public void ParseBlockShouldOutputZeroLengthCodeSpanIfExplicitExpressionIsEmpty()
{
ParseBlockTest("@()",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp().AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldOutputZeroLengthCodeSpanIfEOFOccursAfterStartOfExplicitExpression()
{
ParseBlockTest("@(",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp().AsExpression()
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
LegacyResources.BlockName_ExplicitExpression, ")", "("),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockShouldAcceptEscapedQuoteInNonVerbatimStrings()
{
ParseBlockTest("@(\"\\\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("\"\\\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptEscapedQuoteInVerbatimStrings()
{
ParseBlockTest("@(@\"\"\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("@\"\"\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptMultipleRepeatedEscapedQuoteInVerbatimStrings()
{
ParseBlockTest("@(@\"\"\"\"\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("@\"\"\"\"\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptMultiLineVerbatimStrings()
{
ParseBlockTest(@"@(@""" + Environment.NewLine
+ @"Foo" + Environment.NewLine
+ @"Bar" + Environment.NewLine
+ @"Baz" + Environment.NewLine
+ @""")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code($"@\"{Environment.NewLine}Foo{Environment.NewLine}Bar{Environment.NewLine}Baz{Environment.NewLine}\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptMultipleEscapedQuotesInNonVerbatimStrings()
{
ParseBlockTest("@(\"\\\"hello, world\\\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("\"\\\"hello, world\\\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptMultipleEscapedQuotesInVerbatimStrings()
{
ParseBlockTest("@(@\"\"\"hello, world\"\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("@\"\"\"hello, world\"\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInNonVerbatimStrings()
{
ParseBlockTest("@(\"\\\"\\\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("\"\\\"\\\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockShouldAcceptConsecutiveEscapedQuotesInVerbatimStrings()
{
ParseBlockTest("@(@\"\"\"\"\"\")",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("@\"\"\"\"\"\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
}
}

View File

@ -0,0 +1,297 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpImplicitExpressionTest : CsHtmlCodeParserTestBase
{
private const string TestExtraKeyword = "model";
public static TheoryData NullConditionalOperatorData_Bracket
{
get
{
var noErrors = new RazorError[0];
Func<int, RazorError[]> missingEndParenError = (index) =>
new RazorError[1]
{
new RazorError(
"An opening \"(\" is missing the corresponding closing \")\".",
new SourceLocation(index, 0, index),
length: 1)
};
Func<int, RazorError[]> missingEndBracketError = (index) =>
new RazorError[1]
{
new RazorError(
"An opening \"[\" is missing the corresponding closing \"]\".",
new SourceLocation(index, 0, index),
length: 1)
};
// implicitExpression, expectedImplicitExpression, acceptedCharacters, expectedErrors
return new TheoryData<string, string, AcceptedCharacters, RazorError[]>
{
{ "val??[", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val??[0", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?(", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[more", "val?[more", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?[0]", "val?[0]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[<p>", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val?[more.<p>", "val?[more.", AcceptedCharacters.Any, missingEndBracketError(5) },
{ "val??[more<p>", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[-1]?", "val?[-1]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?[def", "val?[abc]?[def", AcceptedCharacters.Any, missingEndBracketError(11) },
{ "val?[abc]?[2]", "val?[abc]?[2]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?.more?[def]", "val?[abc]?.more?[def]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc]?.more?.abc", "val?[abc]?.more?.abc", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[null ?? true]", "val?[null ?? true]", AcceptedCharacters.NonWhiteSpace, noErrors },
{ "val?[abc?.gef?[-1]]", "val?[abc?.gef?[-1]]", AcceptedCharacters.NonWhiteSpace, noErrors },
};
}
}
[Theory]
[MemberData(nameof(NullConditionalOperatorData_Bracket))]
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Bracket(
string implicitExpresison,
string expectedImplicitExpression,
AcceptedCharacters acceptedCharacters,
RazorError[] expectedErrors)
{
// Act & Assert
ImplicitExpressionTest(
implicitExpresison,
expectedImplicitExpression,
acceptedCharacters,
expectedErrors);
}
public static TheoryData NullConditionalOperatorData_Dot
{
get
{
// implicitExpression, expectedImplicitExpression
return new TheoryData<string, string>
{
{ "val?", "val" },
{ "val??", "val" },
{ "val??more", "val" },
{ "val?!", "val" },
{ "val?.", "val?." },
{ "val??.", "val" },
{ "val?.(abc)", "val?." },
{ "val?.<p>", "val?." },
{ "val?.more", "val?.more" },
{ "val?.more<p>", "val?.more" },
{ "val??.more<p>", "val" },
{ "val?.more(false)?.<p>", "val?.more(false)?." },
{ "val?.more(false)?.abc", "val?.more(false)?.abc" },
{ "val?.more(null ?? true)?.abc", "val?.more(null ?? true)?.abc" },
};
}
}
[Theory]
[MemberData(nameof(NullConditionalOperatorData_Dot))]
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Dot(
string implicitExpresison,
string expectedImplicitExpression)
{
// Act & Assert
ImplicitExpressionTest(implicitExpresison, expectedImplicitExpression);
}
[Fact]
public void NestedImplicitExpression()
{
ParseBlockTest("if (true) { @foo }",
new StatementBlock(
Factory.Code("if (true) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Code(" }").AsStatement()));
}
[Fact]
public void ParseBlockAcceptsNonEnglishCharactersThatAreValidIdentifiers()
{
ImplicitExpressionTest("हळूँजद॔.", "हळूँजद॔");
}
[Fact]
public void ParseBlockOutputsZeroLengthCodeSpanIfInvalidCharacterFollowsTransition()
{
ParseBlockTest("@/",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(KeywordSet)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("/"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockOutputsZeroLengthCodeSpanIfEOFOccursAfterTransition()
{
ParseBlockTest("@",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(KeywordSet)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockSupportsSlashesWithinComplexImplicitExpressions()
{
ImplicitExpressionTest("DataGridColumn.Template(\"Years of Service\", e => (int)Math.Round((DateTime.Now - dt).TotalDays / 365))");
}
[Fact]
public void ParseBlockMethodParsesSingleIdentifierAsImplicitExpression()
{
ImplicitExpressionTest("foo");
}
[Fact]
public void ParseBlockMethodDoesNotAcceptSemicolonIfExpressionTerminatedByWhitespace()
{
ImplicitExpressionTest("foo ;", "foo");
}
[Fact]
public void ParseBlockMethodIgnoresSemicolonAtEndOfSimpleImplicitExpression()
{
RunTrailingSemicolonTest("foo");
}
[Fact]
public void ParseBlockMethodParsesDottedIdentifiersAsImplicitExpression()
{
ImplicitExpressionTest("foo.bar.baz");
}
[Fact]
public void ParseBlockMethodIgnoresSemicolonAtEndOfDottedIdentifiers()
{
RunTrailingSemicolonTest("foo.bar.baz");
}
[Fact]
public void ParseBlockMethodDoesNotIncludeDotAtEOFInImplicitExpression()
{
ImplicitExpressionTest("foo.bar.", "foo.bar");
}
[Fact]
public void ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression()
{
ImplicitExpressionTest("foo.bar.0", "foo.bar");
ImplicitExpressionTest("foo.bar.</p>", "foo.bar");
}
[Fact]
public void ParseBlockMethodDoesNotIncludeSemicolonAfterDot()
{
ImplicitExpressionTest("foo.bar.;", "foo.bar");
}
[Fact]
public void ParseBlockMethodTerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpression()
{
ImplicitExpressionTest("foo.bar</p>", "foo.bar");
}
[Fact]
public void ParseBlockProperlyParsesParenthesesAndBalancesThemInImplicitExpression()
{
ImplicitExpressionTest(@"foo().bar(""bi\""z"", 4)(""chained method; call"").baz(@""bo""""z"", '\'', () => { return 4; }, (4+5+new { foo = bar[4] }))");
}
[Fact]
public void ParseBlockProperlyParsesBracketsAndBalancesThemInImplicitExpression()
{
ImplicitExpressionTest(@"foo.bar[4 * (8 + 7)][""fo\""o""].baz");
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionAtHtmlEndTag()
{
ImplicitExpressionTest("foo().bar.baz</p>zoop", "foo().bar.baz");
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionAtHtmlStartTag()
{
ImplicitExpressionTest("foo().bar.baz<p>zoop", "foo().bar.baz");
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionBeforeDotIfDotNotFollowedByIdentifierStartCharacter()
{
ImplicitExpressionTest("foo().bar.baz.42", "foo().bar.baz");
}
[Fact]
public void ParseBlockStopsBalancingParenthesesAtEOF()
{
ImplicitExpressionTest(
"foo(()", "foo(()",
acceptedCharacters: AcceptedCharacters.Any,
errors: new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(4, 0, 4),
length: 1));
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionIfCloseParenFollowedByAnyWhiteSpace()
{
ImplicitExpressionTest("foo.bar() (baz)", "foo.bar()");
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionIfIdentifierFollowedByAnyWhiteSpace()
{
ImplicitExpressionTest("foo .bar() (baz)", "foo");
}
[Fact]
public void ParseBlockTerminatesImplicitExpressionAtLastValidPointIfDotFollowedByWhitespace()
{
ImplicitExpressionTest("foo. bar() (baz)", "foo");
}
[Fact]
public void ParseBlockOutputExpressionIfModuleTokenNotFollowedByBrace()
{
ImplicitExpressionTest("module.foo()");
}
private void RunTrailingSemicolonTest(string expr)
{
ParseBlockTest(SyntaxConstants.TransitionString + expr + ";",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code(expr)
.AsImplicitExpression(KeywordSet)
.Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
}
}

View File

@ -0,0 +1,104 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpNestedStatementsTest : CsHtmlCodeParserTestBase
{
[Fact]
public void NestedSimpleStatement()
{
ParseBlockTest("@while(true) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void NestedKeywordStatement()
{
ParseBlockTest("@while(true) { for(int i = 0; i < 10; i++) { foo(); } }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { for(int i = 0; i < 10; i++) { foo(); } }")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void NestedCodeBlock()
{
ParseBlockTest("@while(true) { { { { foo(); } } } }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { { { { foo(); } } } }")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void NestedImplicitExpression()
{
ParseBlockTest("@while(true) { @foo }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { ")
.AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Code(" }")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void NestedExplicitExpression()
{
ParseBlockTest("@while(true) { @(foo) }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { ")
.AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(")
.Accepts(AcceptedCharacters.None),
Factory.Code("foo")
.AsExpression(),
Factory.MetaCode(")")
.Accepts(AcceptedCharacters.None)),
Factory.Code(" }")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
[Fact]
public void NestedMarkupBlock()
{
ParseBlockTest("@while(true) { <p>Hello</p> }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) {")
.AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Hello"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("}")
.AsStatement()
.Accepts(AcceptedCharacters.None)));
}
}
}

View File

@ -0,0 +1,423 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpRazorCommentsTest : CsHtmlMarkupParserTestBase
{
[Fact]
public void UnterminatedRazorComment()
{
ParseDocumentTest("@*",
new MarkupBlock(
Factory.EmptyHtml(),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any))),
new RazorError(
LegacyResources.ParseError_RazorComment_Not_Terminated,
SourceLocation.Zero,
length: 2));
}
[Fact]
public void EmptyRazorComment()
{
ParseDocumentTest("@**@",
new MarkupBlock(
Factory.EmptyHtml(),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void RazorCommentInImplicitExpressionMethodCall()
{
ParseDocumentTest("@foo(" + Environment.NewLine
+ "@**@" + Environment.NewLine,
new MarkupBlock(
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo(" + Environment.NewLine)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new CSharpSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
CSharpSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Code(Environment.NewLine)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords))),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(4, 0, 4),
length: 1));
}
[Fact]
public void UnterminatedRazorCommentInImplicitExpressionMethodCall()
{
ParseDocumentTest("@foo(@*",
new MarkupBlock(
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo(")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new CSharpSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
CSharpSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any)))),
new RazorError(
LegacyResources.ParseError_RazorComment_Not_Terminated,
new SourceLocation(5, 0, 5),
length: 2),
new RazorError(
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
new SourceLocation(4, 0, 4),
length: 1));
}
[Fact]
public void RazorCommentInVerbatimBlock()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " <text" + Environment.NewLine
+ " @**@" + Environment.NewLine
+ "}",
new MarkupBlock(
Factory.EmptyHtml(),
new StatementBlock(
Factory.CodeTransition(),
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine)
.AsStatement()
.AutoCompleteWith("}"),
new MarkupBlock(
new MarkupTagBlock(
Factory.MarkupTransition("<text").Accepts(AcceptedCharacters.Any)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None),
Factory.Markup(" ").With(SpanChunkGenerator.Null),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
Factory.Markup("}")))),
new RazorError(
LegacyResources.ParseError_TextTagCannotContainAttributes,
new SourceLocation(7 + Environment.NewLine.Length, 1, 5),
length: 4),
new RazorError(
LegacyResources.FormatParseError_MissingEndTag("text"),
new SourceLocation(7 + Environment.NewLine.Length, 1, 5),
length: 4),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(LegacyResources.BlockName_Code, "}", "{"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void UnterminatedRazorCommentInVerbatimBlock()
{
ParseDocumentTest("@{@*",
new MarkupBlock(
Factory.EmptyHtml(),
new StatementBlock(
Factory.CodeTransition(),
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsStatement()
.AutoCompleteWith("}"),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new CSharpSymbol(Factory.LocationTracker.CurrentLocation,
string.Empty,
CSharpSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any)))),
new RazorError(
LegacyResources.ParseError_RazorComment_Not_Terminated,
new SourceLocation(2, 0, 2),
length: 2),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF(
LegacyResources.BlockName_Code, "}", "{"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void RazorCommentInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@**@" + Environment.NewLine
+ "</p>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup(Environment.NewLine),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
new MarkupTagBlock(
Factory.Markup("</p>"))
));
}
[Fact]
public void MultipleRazorCommentInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ " @**@ " + Environment.NewLine
+ "@**@" + Environment.NewLine
+ "</p>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup(Environment.NewLine),
Factory.Markup(" ").With(SpanChunkGenerator.Null),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(" " + Environment.NewLine).With(SpanChunkGenerator.Null),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
new MarkupTagBlock(
Factory.Markup("</p>"))
));
}
[Fact]
public void MultipleRazorCommentsInSameLineInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@**@ @**@" + Environment.NewLine
+ "</p>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup(Environment.NewLine),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml(),
Factory.Markup(" ").With(SpanChunkGenerator.Null),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
string.Empty,
HtmlSymbolType.Unknown))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
new MarkupTagBlock(
Factory.Markup("</p>"))
));
}
[Fact]
public void RazorCommentsSurroundingMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@* hello *@ content @* world *@" + Environment.NewLine
+ "</p>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup(Environment.NewLine),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
" hello ",
HtmlSymbolType.RazorComment))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(" content "),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
" world ",
HtmlSymbolType.RazorComment))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine),
new MarkupTagBlock(
Factory.Markup("</p>"))
));
}
[Fact]
public void RazorCommentWithExtraNewLineInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine + Environment.NewLine
+ "@* content *@" + Environment.NewLine
+ "@*" + Environment.NewLine
+ "content" + Environment.NewLine
+ "*@" + Environment.NewLine + Environment.NewLine
+ "</p>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup(Environment.NewLine + Environment.NewLine),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
" content ",
HtmlSymbolType.RazorComment))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
new CommentBlock(
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.Span(SpanKind.Comment, new HtmlSymbol(
Factory.LocationTracker.CurrentLocation,
Environment.NewLine + "content" + Environment.NewLine,
HtmlSymbolType.RazorComment))
.Accepts(AcceptedCharacters.Any),
Factory.MetaMarkup("*", HtmlSymbolType.RazorCommentStar)
.Accepts(AcceptedCharacters.None),
Factory.MarkupTransition(HtmlSymbolType.RazorCommentTransition)
.Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).With(SpanChunkGenerator.Null),
Factory.Markup(Environment.NewLine),
new MarkupTagBlock(
Factory.Markup("</p>"))
));
}
}
}

View File

@ -0,0 +1,42 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpReservedWordsTest : CsHtmlCodeParserTestBase
{
[Theory]
[InlineData("namespace")]
[InlineData("class")]
public void ReservedWords(string word)
{
ParseBlockTest(word,
new DirectiveBlock(
Factory.MetaCode(word).Accepts(AcceptedCharacters.None)
),
new RazorError(
LegacyResources.FormatParseError_ReservedWord(word),
SourceLocation.Zero,
word.Length));
}
[Theory]
[InlineData("Namespace")]
[InlineData("Class")]
[InlineData("NAMESPACE")]
[InlineData("CLASS")]
[InlineData("nameSpace")]
[InlineData("NameSpace")]
private void ReservedWordsAreCaseSensitive(string word)
{
ParseBlockTest(word,
new ExpressionBlock(
Factory.Code(word)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
}
}

View File

@ -0,0 +1,588 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpSectionTest : CsHtmlMarkupParserTestBase
{
[Fact]
public void ParseSectionBlockCapturesNewlineImmediatelyFollowing()
{
ParseDocumentTest("@section" + Environment.NewLine,
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator(string.Empty),
Factory.CodeTransition(),
Factory.MetaCode("section" + Environment.NewLine))),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
LegacyResources.ErrorComponent_EndOfFile),
new SourceLocation(8 + Environment.NewLine.Length, 1, 0),
length: 1));
}
[Fact]
public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingOpenBrace()
{
ParseDocumentTest("@section Foo " + Environment.NewLine
+ " ",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("Foo"),
Factory.CodeTransition(),
Factory.MetaCode("section Foo " + Environment.NewLine)),
Factory.Markup(" ")),
new RazorError(
LegacyResources.ParseError_MissingOpenBraceAfterSection,
new SourceLocation(12, 0, 12),
length: 1));
}
[Fact]
public void ParseSectionBlockCapturesWhitespaceToEndOfLineInSectionStatementMissingName()
{
ParseDocumentTest("@section " + Environment.NewLine
+ " ",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator(string.Empty),
Factory.CodeTransition(),
Factory.MetaCode("section " + Environment.NewLine)),
Factory.Markup(" ")),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
LegacyResources.ErrorComponent_EndOfFile),
new SourceLocation(21 + Environment.NewLine.Length, 1, 4),
length: 1));
}
[Fact]
public void ParseSectionBlockIgnoresSectionUnlessAllLowerCase()
{
ParseDocumentTest("@Section foo",
new MarkupBlock(
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Section")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup(" foo")));
}
[Fact]
public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartCharacter()
{
ParseDocumentTest("@section 9 { <p>Foo</p> }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator(string.Empty),
Factory.CodeTransition(),
Factory.MetaCode("section ")),
Factory.Markup("9 { "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" }")),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Section_Name_Start(
LegacyResources.FormatErrorComponent_Character("9")),
new SourceLocation(9, 0, 9),
length: 1));
}
[Fact]
public void ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfNameNotFollowedByOpenBrace()
{
ParseDocumentTest("@section foo-bar { <p>Foo</p> }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo")),
Factory.Markup("-bar { "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" }")),
new RazorError(
LegacyResources.ParseError_MissingOpenBraceAfterSection,
new SourceLocation(12, 0, 12),
length: 1));
}
[Fact]
public void ParserOutputsErrorOnNestedSections()
{
ParseDocumentTest("@section foo { @section bar { <p>Foo</p> } }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new SectionBlock(new SectionChunkGenerator("bar"),
Factory.CodeTransition(),
Factory.MetaCode("section bar {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()),
new RazorError(
LegacyResources.FormatParseError_Sections_Cannot_Be_Nested(LegacyResources.SectionExample_CS),
new SourceLocation(16, 0, 16),
7));
}
[Fact]
public void ParseSectionBlockHandlesEOFAfterOpenBrace()
{
ParseDocumentTest("@section foo {",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith("}", atEndOfSpan: true),
new MarkupBlock())),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(13, 0, 13),
length: 1));
}
[Theory]
[InlineData(" ")]
[InlineData("\n")]
[InlineData(" abc")]
[InlineData(" \n abc")]
public void ParseSectionBlockHandlesEOFAfterOpenContent(string postStartBrace)
{
ParseDocumentTest("@section foo {" + postStartBrace,
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith("}", atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(postStartBrace)))),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(13, 0, 13),
length: 1));
}
[Fact]
public void ParseSectionBlockHandlesUnterminatedSection()
{
ParseDocumentTest("@section foo { <p>Foo{}</p>",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith("}", atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>")),
// Need to provide the markup span as fragments, since the parser will split the {} into separate symbols.
Factory.Markup("Foo", "{", "}"),
new MarkupTagBlock(
Factory.Markup("</p>"))))),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(13, 0, 13),
length: 1));
}
[Fact]
public void ParseSectionBlockHandlesUnterminatedSectionWithNestedIf()
{
var newLine = Environment.NewLine;
var spaces = " ";
ParseDocumentTest(
string.Format(
"@section Test{0}{{{0}{1}@if(true){0}{1}{{{0}{1}{1}<p>Hello World</p>{0}{1}}}",
newLine,
spaces),
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("Test"),
Factory.CodeTransition(),
Factory.MetaCode($"section Test{newLine}{{")
.AutoCompleteWith("}", atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(newLine),
new StatementBlock(
Factory.Code(spaces).AsStatement(),
Factory.CodeTransition(),
Factory.Code($"if(true){newLine}{spaces}{{{newLine}").AsStatement(),
new MarkupBlock(
Factory.Markup($"{spaces}{spaces}"),
BlockFactory.MarkupTagBlock("<p>", AcceptedCharacters.None),
Factory.Markup("Hello World"),
BlockFactory.MarkupTagBlock("</p>", AcceptedCharacters.None),
Factory.Markup(newLine).Accepts(AcceptedCharacters.None)),
Factory.Code($"{spaces}}}").AsStatement())))),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("section", "}", "{"),
new SourceLocation(13 + newLine.Length, 1, 0),
length: 1));
}
[Fact]
public void ParseSectionBlockReportsErrorAndAcceptsWhitespaceToEndOfLineIfSectionNotFollowedByOpenBrace()
{
ParseDocumentTest("@section foo " + Environment.NewLine,
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo " + Environment.NewLine))),
new RazorError(
LegacyResources.ParseError_MissingOpenBraceAfterSection,
new SourceLocation(12, 0, 12),
length: 1));
}
[Fact]
public void ParseSectionBlockAcceptsOpenBraceMultipleLinesBelowSectionName()
{
ParseDocumentTest("@section foo " + Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ "{" + Environment.NewLine
+ "<p>Foo</p>" + Environment.NewLine
+ "}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode(string.Format("section foo {0}{0}{0}{0}{0}{0}{{", Environment.NewLine))
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(Environment.NewLine),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(Environment.NewLine)),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockParsesNamedSectionCorrectly()
{
ParseDocumentTest("@section foo { <p>Foo</p> }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockDoesNotRequireSpaceBetweenSectionNameAndOpenBrace()
{
ParseDocumentTest("@section foo{ <p>Foo</p> }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo{")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>")),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockBalancesBraces()
{
ParseDocumentTest("@section foo { <script>(function foo() { return 1; })();</script> }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<script>")),
Factory.Markup("(function foo() { return 1; })();"),
new MarkupTagBlock(
Factory.Markup("</script>")),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockAllowsBracesInCSharpExpression()
{
ParseDocumentTest("@section foo { I really want to render a close brace, so here I go: @(\"}\") }",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" I really want to render a close brace, so here I go: "),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("\"}\"").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlock()
{
ParseDocumentTest("@section Foo {" + Environment.NewLine
+ "@if(true) {" + Environment.NewLine
+ "}" + Environment.NewLine
+ "}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("Foo"),
Factory.CodeTransition(),
Factory.MetaCode("section Foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.CodeTransition(),
Factory.Code($"if(true) {{{Environment.NewLine}}}{Environment.NewLine}").AsStatement()
)),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlockNoWhitespace()
{
ParseDocumentTest("@section Foo {" + Environment.NewLine
+ "@if(true) {" + Environment.NewLine
+ "}}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("Foo"),
Factory.CodeTransition(),
Factory.MetaCode("section Foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.CodeTransition(),
Factory.Code($"if(true) {{{Environment.NewLine}}}").AsStatement()
)),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockCorrectlyTerminatesWhenCloseBraceImmediatelyFollowsMarkup()
{
ParseDocumentTest("@section foo {something}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("foo"),
Factory.CodeTransition(),
Factory.MetaCode("section foo {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup("something")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockParsesComment()
{
ParseDocumentTest("@section s {<!-- -->}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
Factory.CodeTransition(),
Factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup("<!-- -->")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
// This was a user reported bug (codeplex #710), the section parser wasn't handling
// comments.
[Fact]
public void ParseSectionBlockParsesCommentWithDelimiters()
{
ParseDocumentTest("@section s {<!-- > \" '-->}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
Factory.CodeTransition(),
Factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup("<!-- > \" '-->")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockCommentRecoversFromUnclosedTag()
{
ParseDocumentTest(
"@section s {" + Environment.NewLine + "<a" + Environment.NewLine + "<!-- > \" '-->}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
Factory.CodeTransition(),
Factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(Environment.NewLine),
new MarkupTagBlock(
Factory.Markup("<a" + Environment.NewLine)),
Factory.Markup("<!-- > \" '-->")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
[Fact]
public void ParseSectionBlockParsesXmlProcessingInstruction()
{
ParseDocumentTest(
"@section s { <? xml bleh ?>}",
new MarkupBlock(
Factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
Factory.CodeTransition(),
Factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
Factory.Markup(" <? xml bleh ?>")),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
public static TheoryData SectionWithEscapedTransitionData
{
get
{
var factory = new SpanFactory();
return new TheoryData<string, Block>
{
{
"@section s {<span foo='@@' />}",
new MarkupBlock(
factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
factory.CodeTransition(),
factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockChunkGenerator("foo", new LocationTagged<string>(" foo='", 17, 0, 17), new LocationTagged<string>("'", 25, 0, 25)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged<string>(string.Empty, 23, 0, 23), new LocationTagged<string>("@", 23, 0, 23))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />"))),
factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
factory.EmptyHtml())
},
{
"@section s {<span foo='@DateTime.Now @@' />}",
new MarkupBlock(
factory.EmptyHtml(),
new SectionBlock(new SectionChunkGenerator("s"),
factory.CodeTransition(),
factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockChunkGenerator("foo", new LocationTagged<string>(" foo='", 17, 0, 17), new LocationTagged<string>("'", 39, 0, 39)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged<string>(string.Empty, 23, 0, 23), 23, 0, 23),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged<string>(" ", 36, 0, 36), new LocationTagged<string>("@", 37, 0, 37))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />"))),
factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
factory.EmptyHtml())
},
};
}
}
[Theory]
[MemberData(nameof(SectionWithEscapedTransitionData))]
public void ParseSectionBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
{
ParseDocumentTest(input, expected);
}
}
}

View File

@ -0,0 +1,217 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpSpecialBlockTest : CsHtmlCodeParserTestBase
{
[Fact]
public void ParseInheritsStatementMarksInheritsSpanAsCanGrowIfMissingTrailingSpace()
{
ParseBlockTest("inherits",
new DirectiveBlock(
Factory.MetaCode("inherits").Accepts(AcceptedCharacters.Any)
),
new RazorError(
LegacyResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName,
new SourceLocation(0, 0, 0), 8));
}
[Fact]
public void InheritsBlockAcceptsMultipleGenericArguments()
{
ParseBlockTest("inherits Foo.Bar<Biz<Qux>, string, int>.Baz",
new DirectiveBlock(
Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
Factory.Code("Foo.Bar<Biz<Qux>, string, int>.Baz")
.AsBaseType("Foo.Bar<Biz<Qux>, string, int>.Baz")
));
}
[Fact]
public void InheritsBlockOutputsErrorIfInheritsNotFollowedByTypeButAcceptsEntireLineAsCode()
{
ParseBlockTest("inherits " + Environment.NewLine
+ "foo",
new DirectiveBlock(
Factory.MetaCode("inherits ").Accepts(AcceptedCharacters.None),
Factory.Code(" " + Environment.NewLine)
.AsBaseType(string.Empty)
),
new RazorError(LegacyResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName, 0, 0, 0, 8));
}
[Fact]
public void NamespaceImportInsideCodeBlockCausesError()
{
ParseBlockTest("{ using Foo.Bar.Baz; var foo = bar; }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" using Foo.Bar.Baz; var foo = bar; ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
),
new RazorError(
LegacyResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
new SourceLocation(2, 0, 2),
length: 5));
}
[Fact]
public void TypeAliasInsideCodeBlockIsNotHandledSpecially()
{
ParseBlockTest("{ using Foo = Bar.Baz; var foo = bar; }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" using Foo = Bar.Baz; var foo = bar; ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
),
new RazorError(
LegacyResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock,
new SourceLocation(2, 0, 2),
length: 5));
}
[Fact]
public void Plan9FunctionsKeywordInsideCodeBlockIsNotHandledSpecially()
{
ParseBlockTest("{ functions Foo; }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" functions Foo; ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void NonKeywordStatementInCodeBlockIsHandledCorrectly()
{
ParseBlockTest("{" + Environment.NewLine
+ " List<dynamic> photos = gallery.Photo.ToList();" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code($"{Environment.NewLine} List<dynamic> photos = gallery.Photo.ToList();{Environment.NewLine}")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockBalancesBracesOutsideStringsIfFirstCharacterIsBraceAndReturnsSpanOfTypeCode()
{
// Arrange
const string code = "foo\"b}ar\" if(condition) { string.Format(\"{0}\"); } ";
// Act/Assert
ParseBlockTest("{" + code + "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(code)
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockBalancesParensOutsideStringsIfFirstCharacterIsParenAndReturnsSpanOfTypeExpression()
{
// Arrange
const string code = "foo\"b)ar\" if(condition) { string.Format(\"{0}\"); } ";
// Act/Assert
ParseBlockTest("(" + code + ")",
new ExpressionBlock(
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code(code).AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockBalancesBracesAndOutputsContentAsClassLevelCodeSpanIfFirstIdentifierIsFunctionsKeyword()
{
const string code = " foo(); \"bar}baz\" ";
ParseBlockTest("functions {" + code + "} zoop",
new FunctionsBlock(
Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
Factory.Code(code)
.AsFunctionsBody()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockDoesNoErrorRecoveryForFunctionsBlock()
{
ParseBlockTest("functions { { { { { } zoop",
new FunctionsBlock(
Factory.MetaCode("functions {").Accepts(AcceptedCharacters.None),
Factory.Code(" { { { { } zoop")
.AsFunctionsBody()
.AutoCompleteWith("}")
),
new RazorError(
LegacyResources.FormatParseError_Expected_EndOfBlock_Before_EOF("functions", "}", "{"),
new SourceLocation(10, 0, 10),
length: 1));
}
[Fact]
public void ParseBlockIgnoresFunctionsUnlessAllLowerCase()
{
ParseBlockTest("Functions { foo() }",
new ExpressionBlock(
Factory.Code("Functions")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)));
}
[Fact]
public void ParseBlockIgnoresSingleSlashAtStart()
{
ParseBlockTest("@/ foo",
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp()
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
new RazorError(
LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("/"),
new SourceLocation(1, 0, 1),
length: 1));
}
[Fact]
public void ParseBlockTerminatesSingleLineCommentAtEndOfLine()
{
ParseBlockTest("if(!false) {" + Environment.NewLine
+ " // Foo" + Environment.NewLine
+ "\t<p>A real tag!</p>" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code($"if(!false) {{{Environment.NewLine} // Foo{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup("\t"),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("A real tag!"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
Factory.Code("}").AsStatement()
));
}
}
}

View File

@ -0,0 +1,418 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// Basic Tests for C# Statements:
// * Basic case for each statement
// * Basic case for ALL clauses
// This class DOES NOT contain
// * Error cases
// * Tests for various types of nested statements
// * Comment tests
internal class CSharpStatementTest : CsHtmlCodeParserTestBase
{
[Fact]
public void ForStatement()
{
ParseBlockTest("@for(int i = 0; i++; i < length) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("for(int i = 0; i++; i < length) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ForEachStatement()
{
ParseBlockTest("@foreach(var foo in bar) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("foreach(var foo in bar) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void WhileStatement()
{
ParseBlockTest("@while(true) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("while(true) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void SwitchStatement()
{
ParseBlockTest("@switch(foo) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("switch(foo) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void LockStatement()
{
ParseBlockTest("@lock(baz) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("lock(baz) { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void IfStatement()
{
ParseBlockTest("@if(true) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("if(true) { foo(); }")
.AsStatement()
));
}
[Fact]
public void ElseIfClause()
{
ParseBlockTest("@if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }")
.AsStatement()
));
}
[Fact]
public void ElseClause()
{
ParseBlockTest("@if(true) { foo(); } else { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("if(true) { foo(); } else { foo(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void TryStatement()
{
ParseBlockTest("@try { foo(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("try { foo(); }")
.AsStatement()
));
}
[Fact]
public void CatchClause()
{
ParseBlockTest("@try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }")
.AsStatement()
));
}
public static TheoryData ExceptionFilterData
{
get
{
var factory = new SpanFactory();
// document, expectedStatement
return new TheoryData<string, StatementBlock>
{
{
"@try { someMethod(); } catch(Exception) when (true) { handleIO(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
.AsStatement())
},
{
"@try { A(); } catch(Exception) when (true) { B(); } finally { C(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { A(); } catch(Exception) when (true) { B(); } finally { C(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None))
},
{
"@try { A(); } catch(Exception) when (true) { B(); } catch(IOException) when (false) { C(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { A(); } catch(Exception) when (true) { B(); } catch(IOException) " +
"when (false) { C(); }")
.AsStatement())
},
{
string.Format("@try{0}{{{0} A();{0}}}{0}catch(Exception) when (true)", Environment.NewLine) +
string.Format("{0}{{{0} B();{0}}}{0}catch(IOException) when (false)", Environment.NewLine) +
string.Format("{0}{{{0} C();{0}}}", Environment.NewLine),
new StatementBlock(
factory.CodeTransition(),
factory
.Code(
string.Format("try{0}{{{0} A();{0}}}{0}catch(Exception) ", Environment.NewLine) +
string.Format("when (true){0}{{{0} B();{0}}}{0}", Environment.NewLine) +
string.Format("catch(IOException) when (false){0}{{{0} ", Environment.NewLine) +
string.Format("C();{0}}}", Environment.NewLine))
.AsStatement())
},
// Wrapped in @{ block.
{
"@{try { someMethod(); } catch(Exception) when (true) { handleIO(); }}",
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharacters.None),
factory
.Code("try { someMethod(); } catch(Exception) when (true) { handleIO(); }")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
factory.MetaCode("}").Accepts(AcceptedCharacters.None))
},
// Partial exception filter data
{
"@try { someMethod(); } catch(Exception) when",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when")
.AsStatement())
},
{
"@try { someMethod(); } when",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); }")
.AsStatement())
},
{
"@try { someMethod(); } catch(Exception) when { anotherMethod(); }",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when { anotherMethod(); }")
.AsStatement())
},
{
"@try { someMethod(); } catch(Exception) when (true)",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true)")
.AsStatement())
},
};
}
}
[Theory]
[MemberData(nameof(ExceptionFilterData))]
public void ExceptionFilters(string document, StatementBlock expectedStatement)
{
// Act & Assert
ParseBlockTest(document, expectedStatement);
}
public static TheoryData ExceptionFilterErrorData
{
get
{
var factory = new SpanFactory();
var unbalancedParenErrorString = "An opening \"(\" is missing the corresponding closing \")\".";
var unbalancedBracketCatchErrorString = "The catch block is missing a closing \"}\" character. " +
"Make sure you have a matching \"}\" character for all the \"{\" characters within this block, " +
"and that none of the \"}\" characters are being interpreted as markup.";
// document, expectedStatement, expectedErrors
return new TheoryData<string, StatementBlock, RazorError[]>
{
{
"@try { someMethod(); } catch(Exception) when (",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (")
.AsStatement()),
new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45, 1) }
},
{
"@try { someMethod(); } catch(Exception) when (someMethod(",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (someMethod(")
.AsStatement()),
new[] { new RazorError(unbalancedParenErrorString, 45, 0, 45, 1) }
},
{
"@try { someMethod(); } catch(Exception) when (true) {",
new StatementBlock(
factory.CodeTransition(),
factory
.Code("try { someMethod(); } catch(Exception) when (true) {")
.AsStatement()),
new[] { new RazorError(unbalancedBracketCatchErrorString, 23, 0, 23, 1) }
},
};
}
}
[Theory]
[MemberData(nameof(ExceptionFilterErrorData))]
public void ExceptionFilterErrors(
string document,
StatementBlock expectedStatement,
RazorError[] expectedErrors)
{
// Act & Assert
ParseBlockTest(document, expectedStatement, expectedErrors);
}
[Fact]
public void FinallyClause()
{
ParseBlockTest("@try { foo(); } finally { Dispose(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("try { foo(); } finally { Dispose(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
public static TheoryData StaticUsingData
{
get
{
var factory = new SpanFactory();
Func<string, string, DirectiveBlock> createUsing = (code, import) =>
new DirectiveBlock(
factory.CodeTransition(),
factory.Code(code)
.AsNamespaceImport(import)
.Accepts(AcceptedCharacters.AnyExceptNewline));
// document, expectedResult
return new TheoryData<string, DirectiveBlock>
{
{ "@using static", createUsing("using static", " static") },
{ "@using static ", createUsing("using static ", " static ") },
{ "@using static ", createUsing("using static ", " static ") },
{ "@using static System", createUsing("using static System", " static System") },
{
"@using static System",
createUsing("using static System", " static System")
},
{
"@using static System.Console",
createUsing("using static System.Console", " static System.Console")
},
{
"@using static global::System.Console",
createUsing("using static global::System.Console", " static global::System.Console")
},
{
"@using static global::System.Console ",
createUsing("using static global::System.Console", " static global::System.Console")
},
};
}
}
[Theory]
[MemberData(nameof(StaticUsingData))]
public void StaticUsingImport(string document, DirectiveBlock expectedResult)
{
// Act & Assert
ParseBlockTest(document, expectedResult);
}
[Fact]
public void UsingStatement()
{
ParseBlockTest("@using(var foo = new Foo()) { foo.Bar(); }",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("using(var foo = new Foo()) { foo.Bar(); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void UsingTypeAlias()
{
ParseBlockTest("@using StringDictionary = System.Collections.Generic.Dictionary<string, string>",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.Code("using StringDictionary = System.Collections.Generic.Dictionary<string, string>")
.AsNamespaceImport(" StringDictionary = System.Collections.Generic.Dictionary<string, string>")
.Accepts(AcceptedCharacters.AnyExceptNewline)
));
}
[Fact]
public void UsingNamespaceImport()
{
ParseBlockTest("@using System.Text.Encoding.ASCIIEncoding",
new DirectiveBlock(
Factory.CodeTransition(),
Factory.Code("using System.Text.Encoding.ASCIIEncoding")
.AsNamespaceImport(" System.Text.Encoding.ASCIIEncoding")
.Accepts(AcceptedCharacters.AnyExceptNewline)
));
}
[Fact]
public void DoStatement()
{
ParseBlockTest("@do { foo(); } while(true);",
new StatementBlock(
Factory.CodeTransition(),
Factory.Code("do { foo(); } while(true);")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void NonBlockKeywordTreatedAsImplicitExpression()
{
ParseBlockTest("@is foo",
new ExpressionBlock(new ExpressionChunkGenerator(),
Factory.CodeTransition(),
Factory.Code("is")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
}
}

View File

@ -0,0 +1,321 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTemplateTest : CsHtmlCodeParserTestBase
{
private const string TestTemplateCode = " @<p>Foo #@item</p>";
private TemplateBlock TestTemplate()
{
return new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo #"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("item")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
);
}
private const string TestNestedTemplateCode = " @<p>Foo #@Html.Repeat(10, @<p>@item</p>)</p>";
private TemplateBlock TestNestedTemplate()
{
return new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo #"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Html.Repeat(10, ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("item")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
);
}
[Fact]
public void ParseBlockHandlesSingleLineTemplate()
{
ParseBlockTest("{ var foo = @: bar" + Environment.NewLine
+ "; }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup(" bar" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
.Accepts(AcceptedCharacters.None)
)
),
Factory.Code("; ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockHandlesSingleLineImmediatelyFollowingStatementChar()
{
ParseBlockTest("{i@: bar" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code("i")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup(" bar" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString))
.Accepts(AcceptedCharacters.None)
)
),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockHandlesSimpleTemplateInExplicitExpressionParens()
{
ParseBlockTest("(Html.Repeat(10," + TestTemplateCode + "))",
new ExpressionBlock(
Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
Factory.Code("Html.Repeat(10, ").AsExpression(),
TestTemplate(),
Factory.Code(")").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockHandlesSimpleTemplateInImplicitExpressionParens()
{
ParseBlockTest("Html.Repeat(10," + TestTemplateCode + ")",
new ExpressionBlock(
Factory.Code("Html.Repeat(10, ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
TestTemplate(),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
[Fact]
public void ParseBlockHandlesTwoTemplatesInImplicitExpressionParens()
{
ParseBlockTest("Html.Repeat(10," + TestTemplateCode + "," + TestTemplateCode + ")",
new ExpressionBlock(
Factory.Code("Html.Repeat(10, ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
TestTemplate(),
Factory.Code(", ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
TestTemplate(),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
[Fact]
public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInImplicitExpressionParens()
{
ParseBlockTest("Html.Repeat(10," + TestNestedTemplateCode + ")",
new ExpressionBlock(
Factory.Code("Html.Repeat(10, ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
TestNestedTemplate(),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
GetNestedTemplateError(42));
}
[Fact]
public void ParseBlockHandlesSimpleTemplateInStatementWithinCodeBlock()
{
ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
new StatementBlock(
Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
TestTemplate(),
Factory.Code("); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockHandlesTwoTemplatesInStatementWithinCodeBlock()
{
ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
new StatementBlock(
Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ").AsStatement(),
TestTemplate(),
Factory.Code(", ").AsStatement(),
TestTemplate(),
Factory.Code("); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinCodeBlock()
{
ParseBlockTest("foreach(foo in Bar) { Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
new StatementBlock(
Factory.Code("foreach(foo in Bar) { Html.ExecuteTemplate(foo, ")
.AsStatement(),
TestNestedTemplate(),
Factory.Code("); }")
.AsStatement()
.Accepts(AcceptedCharacters.None)
),
GetNestedTemplateError(74));
}
[Fact]
public void ParseBlockHandlesSimpleTemplateInStatementWithinStatementBlock()
{
ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "); }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
TestTemplate(),
Factory.Code("); ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockHandlessTwoTemplatesInStatementWithinStatementBlock()
{
ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestTemplateCode + "," + TestTemplateCode + "); }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
TestTemplate(),
Factory.Code(", ").AsStatement(),
TestTemplate(),
Factory.Code("); ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinStatementBlock()
{
ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + TestNestedTemplateCode + "); }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
TestNestedTemplate(),
Factory.Code("); ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
),
GetNestedTemplateError(69));
}
[Fact]
public void ParseBlock_WithDoubleTransition_DoesNotThrow()
{
// Arrange
var testTemplateWithDoubleTransitionCode = " @<p foo='@@'>Foo #@item</p>";
var testTemplateWithDoubleTransition = new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p"),
new MarkupBlock(
new AttributeBlockChunkGenerator("foo", new LocationTagged<string>(" foo='", 46, 0, 46), new LocationTagged<string>("'", 54, 0, 54)),
Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged<string>(string.Empty, 52, 0, 52), new LocationTagged<string>("@", 52, 0, 52))).Accepts(AcceptedCharacters.None),
Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(">").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo #"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("item")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
);
var expected = new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
testTemplateWithDoubleTransition,
Factory.Code("); ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None));
// Act & Assert
ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + testTemplateWithDoubleTransitionCode + "); }", expected);
}
private static RazorError GetNestedTemplateError(int characterIndex)
{
return new RazorError(
LegacyResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested,
new SourceLocation(characterIndex, 0, characterIndex),
length: 1);
}
}
}

View File

@ -0,0 +1,693 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpToMarkupSwitchTest : CsHtmlCodeParserTestBase
{
[Fact]
public void SingleAngleBracketDoesNotCauseSwitchIfOuterBlockIsTerminated()
{
ParseBlockTest("{ List< }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" List< ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockGivesSpacesToCodeOnAtTagTemplateTransitionInDesignTimeMode()
{
ParseBlockTest("Foo( @<p>Foo</p> )",
new ExpressionBlock(
Factory.Code("Foo( ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.Any),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
),
Factory.Code(" )")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
), designTime: true);
}
[Fact]
public void ParseBlockGivesSpacesToCodeOnAtColonTemplateTransitionInDesignTimeMode()
{
ParseBlockTest("Foo( " + Environment.NewLine
+ "@:<p>Foo</p> " + Environment.NewLine
+ ")",
new ExpressionBlock(
Factory.Code("Foo( " + Environment.NewLine).AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("<p>Foo</p> " + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
)
),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
), designTime: true);
}
[Fact]
public void ParseBlockGivesSpacesToCodeOnTagTransitionInDesignTimeMode()
{
ParseBlockTest("{" + Environment.NewLine
+ " <p>Foo</p> " + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
),
Factory.Code(" " + Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
), designTime: true);
}
[Fact]
public void ParseBlockGivesSpacesToCodeOnInvalidAtTagTransitionInDesignTimeMode()
{
ParseBlockTest("{" + Environment.NewLine
+ " @<p>Foo</p> " + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
),
Factory.Code(" " + Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
), true,
new RazorError(
LegacyResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start,
new SourceLocation(5 + Environment.NewLine.Length, 1, 4),
length: 1));
}
[Fact]
public void ParseBlockGivesSpacesToCodeOnAtColonTransitionInDesignTimeMode()
{
ParseBlockTest("{" + Environment.NewLine
+ " @:<p>Foo</p> " + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("<p>Foo</p> " + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
), designTime: true);
}
[Fact]
public void ParseBlockShouldSupportSingleLineMarkupContainingStatementBlock()
{
ParseBlockTest("Repeat(10," + Environment.NewLine
+ " @: @{}" + Environment.NewLine
+ ")",
new ExpressionBlock(
Factory.Code($"Repeat(10,{Environment.NewLine} ")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords),
new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup(" ")
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString)),
new StatementBlock(
Factory.CodeTransition(),
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.EmptyCSharp()
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
),
Factory.Markup(Environment.NewLine)
.Accepts(AcceptedCharacters.None)
)
),
Factory.Code(")")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
));
}
[Fact]
public void ParseBlockShouldSupportMarkupWithoutPreceedingWhitespace()
{
ParseBlockTest("foreach(var file in files){" + Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ "@:Baz" + Environment.NewLine
+ "<br/>" + Environment.NewLine
+ "<a>Foo</a>" + Environment.NewLine
+ "@:Bar" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code(string.Format("foreach(var file in files){{{0}{0}{0}", Environment.NewLine)).AsStatement(),
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Baz" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<br/>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<a>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</a>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Bar" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockGivesAllWhitespaceOnSameLineExcludingPreceedingNewlineButIncludingTrailingNewLineToMarkup()
{
ParseBlockTest("if(foo) {" + Environment.NewLine
+ " var foo = \"After this statement there are 10 spaces\"; " + Environment.NewLine
+ " <p>" + Environment.NewLine
+ " Foo" + Environment.NewLine
+ " @bar" + Environment.NewLine
+ " </p>" + Environment.NewLine
+ " @:Hello!" + Environment.NewLine
+ " var biz = boz;" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code(
$"if(foo) {{{Environment.NewLine} var foo = \"After this statement there are " +
"10 spaces\"; " + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup($"{Environment.NewLine} Foo{Environment.NewLine}"),
new ExpressionBlock(
Factory.Code(" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("bar").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
),
Factory.Markup(Environment.NewLine + " "),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Hello!" + Environment.NewLine).With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code($" var biz = boz;{Environment.NewLine}}}").AsStatement()));
}
[Fact]
public void ParseBlockAllowsMarkupInIfBodyWithBraces()
{
ParseBlockTest("if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> }",
new StatementBlock(
Factory.Code("if(foo) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Bar"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} else if(bar) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Baz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} else {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Boz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockAllowsMarkupInIfBodyWithBracesWithinCodeBlock()
{
ParseBlockTest("{ if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> } }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" if(foo) {")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Bar"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} else if(bar) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Baz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} else {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Boz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitch()
{
// Arrange
ParseBlockTest("switch(foo) {" + Environment.NewLine
+ " case 0:" + Environment.NewLine
+ " <p>Foo</p>" + Environment.NewLine
+ " break;" + Environment.NewLine
+ " case 1:" + Environment.NewLine
+ " <p>Bar</p>" + Environment.NewLine
+ " return;" + Environment.NewLine
+ " case 2:" + Environment.NewLine
+ " {" + Environment.NewLine
+ " <p>Baz</p>" + Environment.NewLine
+ " <p>Boz</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " default:" + Environment.NewLine
+ " <p>Biz</p>" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code($"switch(foo) {{{Environment.NewLine} case 0:{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code($" break;{Environment.NewLine} case 1:{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Bar"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code(
$" return;{Environment.NewLine} case 2:{Environment.NewLine}" +
" {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Baz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Boz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code($" }}{Environment.NewLine} default:{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Biz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSupportsMarkupInCaseAndDefaultBranchesOfSwitchInCodeBlock()
{
// Arrange
ParseBlockTest("{ switch(foo) {" + Environment.NewLine
+ " case 0:" + Environment.NewLine
+ " <p>Foo</p>" + Environment.NewLine
+ " break;" + Environment.NewLine
+ " case 1:" + Environment.NewLine
+ " <p>Bar</p>" + Environment.NewLine
+ " return;" + Environment.NewLine
+ " case 2:" + Environment.NewLine
+ " {" + Environment.NewLine
+ " <p>Baz</p>" + Environment.NewLine
+ " <p>Boz</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " default:" + Environment.NewLine
+ " <p>Biz</p>" + Environment.NewLine
+ "} }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code($" switch(foo) {{{Environment.NewLine} case 0:{Environment.NewLine}")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code($" break;{Environment.NewLine} case 1:{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Bar"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code(
$" return;{Environment.NewLine} case 2:{Environment.NewLine}" +
" {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Baz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Boz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code($" }}{Environment.NewLine} default:{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Biz"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code("} ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnOpenAngleBracket()
{
ParseBlockTest("for(int i = 0; i < 10; i++) { <p>Foo</p> }",
new StatementBlock(
Factory.Code("for(int i = 0; i < 10; i++) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("}").AsStatement().Accepts(AcceptedCharacters.None)
));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnOpenAngleBracketInCodeBlock()
{
ParseBlockTest("{ for(int i = 0; i < 10; i++) { <p>Foo</p> } }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" for(int i = 0; i < 10; i++) {")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(" ").Accepts(AcceptedCharacters.None)
),
Factory.Code("} ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColon()
{
// Arrange
ParseBlockTest("if(foo) { @:Bar" + Environment.NewLine
+ "} zoop",
new StatementBlock(
Factory.Code("if(foo) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Bar" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code("}").AsStatement()));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByDoubleColon()
{
// Arrange
ParseBlockTest("if(foo) { @::Sometext" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code("if(foo) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup(":Sometext" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code("}").AsStatement()));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByTripleColon()
{
// Arrange
ParseBlockTest("if(foo) { @:::Sometext" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.Code("if(foo) {").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("::Sometext" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code("}").AsStatement()));
}
[Fact]
public void ParseBlockParsesMarkupStatementOnSwitchCharacterFollowedByColonInCodeBlock()
{
// Arrange
ParseBlockTest("{ if(foo) { @:Bar" + Environment.NewLine
+ "} } zoop",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" if(foo) {")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Bar" + Environment.NewLine).Accepts(AcceptedCharacters.None)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code("} ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTag()
{
ParseBlockTest("if (i > 0) { <text>;</text> }",
new StatementBlock(
Factory.Code("if (i > 0) {").AsStatement(),
new MarkupBlock(
new MarkupTagBlock(
Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None)),
Factory.Markup(";").Accepts(AcceptedCharacters.None),
new MarkupTagBlock(
Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None))),
Factory.Code(" }").AsStatement()));
}
[Fact]
public void ParseBlockCorrectlyReturnsFromMarkupBlockWithPseudoTagInCodeBlock()
{
ParseBlockTest("{ if (i > 0) { <text>;</text> } }",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" if (i > 0) {")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
new MarkupTagBlock(
Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None)),
Factory.Markup(";").Accepts(AcceptedCharacters.None),
new MarkupTagBlock(
Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None))),
Factory.Code(" } ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void ParseBlockSupportsAllKindsOfImplicitMarkupInCodeBlock()
{
ParseBlockTest("{" + Environment.NewLine
+ " if(true) {" + Environment.NewLine
+ " @:Single Line Markup" + Environment.NewLine
+ " }" + Environment.NewLine
+ " foreach (var p in Enumerable.Range(1, 10)) {" + Environment.NewLine
+ " <text>The number is @p</text>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " if(!false) {" + Environment.NewLine
+ " <p>A real tag!</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ "}",
new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code($"{Environment.NewLine} if(true) {{{Environment.NewLine}")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new MarkupBlock(
Factory.Markup(" "),
Factory.MarkupTransition(),
Factory.MetaMarkup(":", HtmlSymbolType.Colon),
Factory.Markup("Single Line Markup" + Environment.NewLine)
.With(new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString, AcceptedCharacters.None))
),
Factory.Code($" }}{Environment.NewLine} foreach (var p in Enumerable.Range(1, 10)) {{{Environment.NewLine}").AsStatement(),
new MarkupBlock(
new MarkupTagBlock(
Factory.MarkupTransition("<text>").Accepts(AcceptedCharacters.None)),
Factory.Markup("The number is ").Accepts(AcceptedCharacters.None),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.MarkupTransition("</text>").Accepts(AcceptedCharacters.None))),
Factory.Code($"{Environment.NewLine} }}{Environment.NewLine} if(!false) {{{Environment.NewLine}").AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
new MarkupTagBlock(
Factory.Markup("<p>").Accepts(AcceptedCharacters.None)),
Factory.Markup("A real tag!"),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None)),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharacters.None)
),
Factory.Code(" }" + Environment.NewLine).AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)));
}
}
}

View File

@ -0,0 +1,89 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizerCommentTest : CSharpTokenizerTestBase
{
[Fact]
public void Next_Ignores_Star_At_EOF_In_RazorComment()
{
TestTokenizer("@* Foo * Bar * Baz *",
new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz *", CSharpSymbolType.RazorComment));
}
[Fact]
public void Next_Ignores_Star_Without_Trailing_At()
{
TestTokenizer("@* Foo * Bar * Baz *@",
new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
new CSharpSymbol(2, 0, 2, " Foo * Bar * Baz ", CSharpSymbolType.RazorComment),
new CSharpSymbol(19, 0, 19, "*", CSharpSymbolType.RazorCommentStar),
new CSharpSymbol(20, 0, 20, "@", CSharpSymbolType.RazorCommentTransition));
}
[Fact]
public void Next_Returns_RazorComment_Token_For_Entire_Razor_Comment()
{
TestTokenizer("@* Foo Bar Baz *@",
new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.RazorCommentTransition),
new CSharpSymbol(1, 0, 1, "*", CSharpSymbolType.RazorCommentStar),
new CSharpSymbol(2, 0, 2, " Foo Bar Baz ", CSharpSymbolType.RazorComment),
new CSharpSymbol(15, 0, 15, "*", CSharpSymbolType.RazorCommentStar),
new CSharpSymbol(16, 0, 16, "@", CSharpSymbolType.RazorCommentTransition));
}
[Fact]
public void Next_Returns_Comment_Token_For_Entire_Single_Line_Comment()
{
TestTokenizer("// Foo Bar Baz", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment));
}
[Fact]
public void Single_Line_Comment_Is_Terminated_By_Newline()
{
TestTokenizer("// Foo Bar Baz\na", new CSharpSymbol(0, 0, 0, "// Foo Bar Baz", CSharpSymbolType.Comment), IgnoreRemaining);
}
[Fact]
public void Multi_Line_Comment_In_Single_Line_Comment_Has_No_Effect()
{
TestTokenizer("// Foo/*Bar*/ Baz\na", new CSharpSymbol(0, 0, 0, "// Foo/*Bar*/ Baz", CSharpSymbolType.Comment), IgnoreRemaining);
}
[Fact]
public void Next_Returns_Comment_Token_For_Entire_Multi_Line_Comment()
{
TestTokenizer("/* Foo\nBar\nBaz */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment));
}
[Fact]
public void Multi_Line_Comment_Is_Terminated_By_End_Sequence()
{
TestTokenizer("/* Foo\nBar\nBaz */a", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz */", CSharpSymbolType.Comment), IgnoreRemaining);
}
[Fact]
public void Unterminated_Multi_Line_Comment_Captures_To_EOF()
{
TestTokenizer("/* Foo\nBar\nBaz", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz", CSharpSymbolType.Comment), IgnoreRemaining);
}
[Fact]
public void Nested_Multi_Line_Comments_Terminated_At_First_End_Sequence()
{
TestTokenizer("/* Foo/*\nBar\nBaz*/ */", new CSharpSymbol(0, 0, 0, "/* Foo/*\nBar\nBaz*/", CSharpSymbolType.Comment), IgnoreRemaining);
}
[Fact]
public void Nested_Multi_Line_Comments_Terminated_At_Full_End_Sequence()
{
TestTokenizer("/* Foo\nBar\nBaz* */", new CSharpSymbol(0, 0, 0, "/* Foo\nBar\nBaz* */", CSharpSymbolType.Comment), IgnoreRemaining);
}
}
}

View File

@ -0,0 +1,170 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizerIdentifierTest : CSharpTokenizerTestBase
{
[Fact]
public void Simple_Identifier_Is_Recognized()
{
TestTokenizer("foo", new CSharpSymbol(0, 0, 0, "foo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Starting_With_Underscore_Is_Recognized()
{
TestTokenizer("_foo", new CSharpSymbol(0, 0, 0, "_foo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Digits()
{
TestTokenizer("foo4", new CSharpSymbol(0, 0, 0, "foo4", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Start_With_Titlecase_Letter()
{
TestTokenizer("ῼfoo", new CSharpSymbol(0, 0, 0, "ῼfoo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Start_With_Letter_Modifier()
{
TestTokenizer("ᵊfoo", new CSharpSymbol(0, 0, 0, "ᵊfoo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Start_With_Other_Letter()
{
TestTokenizer("ƻfoo", new CSharpSymbol(0, 0, 0, "ƻfoo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Start_With_Number_Letter()
{
TestTokenizer("ool", new CSharpSymbol(0, 0, 0, "ool", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Non_Spacing_Mark()
{
TestTokenizer("foo\u0300", new CSharpSymbol(0, 0, 0, "foo\u0300", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Spacing_Combining_Mark()
{
TestTokenizer("foo", new CSharpSymbol(0, 0, 0, "foo", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Non_English_Digit()
{
TestTokenizer("foo١", new CSharpSymbol(0, 0, 0, "foo١", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Connector_Punctuation()
{
TestTokenizer("foo‿bar", new CSharpSymbol(0, 0, 0, "foo‿bar", CSharpSymbolType.Identifier));
}
[Fact]
public void Identifier_Can_Contain_Format_Character()
{
TestTokenizer("foo؃bar", new CSharpSymbol(0, 0, 0, "foo؃bar", CSharpSymbolType.Identifier));
}
[Fact]
public void Keywords_Are_Recognized_As_Keyword_Tokens()
{
TestKeyword("abstract", CSharpKeyword.Abstract);
TestKeyword("byte", CSharpKeyword.Byte);
TestKeyword("class", CSharpKeyword.Class);
TestKeyword("delegate", CSharpKeyword.Delegate);
TestKeyword("event", CSharpKeyword.Event);
TestKeyword("fixed", CSharpKeyword.Fixed);
TestKeyword("if", CSharpKeyword.If);
TestKeyword("internal", CSharpKeyword.Internal);
TestKeyword("new", CSharpKeyword.New);
TestKeyword("override", CSharpKeyword.Override);
TestKeyword("readonly", CSharpKeyword.Readonly);
TestKeyword("short", CSharpKeyword.Short);
TestKeyword("struct", CSharpKeyword.Struct);
TestKeyword("try", CSharpKeyword.Try);
TestKeyword("unsafe", CSharpKeyword.Unsafe);
TestKeyword("volatile", CSharpKeyword.Volatile);
TestKeyword("as", CSharpKeyword.As);
TestKeyword("do", CSharpKeyword.Do);
TestKeyword("is", CSharpKeyword.Is);
TestKeyword("params", CSharpKeyword.Params);
TestKeyword("ref", CSharpKeyword.Ref);
TestKeyword("switch", CSharpKeyword.Switch);
TestKeyword("ushort", CSharpKeyword.Ushort);
TestKeyword("while", CSharpKeyword.While);
TestKeyword("case", CSharpKeyword.Case);
TestKeyword("const", CSharpKeyword.Const);
TestKeyword("explicit", CSharpKeyword.Explicit);
TestKeyword("float", CSharpKeyword.Float);
TestKeyword("null", CSharpKeyword.Null);
TestKeyword("sizeof", CSharpKeyword.Sizeof);
TestKeyword("typeof", CSharpKeyword.Typeof);
TestKeyword("implicit", CSharpKeyword.Implicit);
TestKeyword("private", CSharpKeyword.Private);
TestKeyword("this", CSharpKeyword.This);
TestKeyword("using", CSharpKeyword.Using);
TestKeyword("extern", CSharpKeyword.Extern);
TestKeyword("return", CSharpKeyword.Return);
TestKeyword("stackalloc", CSharpKeyword.Stackalloc);
TestKeyword("uint", CSharpKeyword.Uint);
TestKeyword("base", CSharpKeyword.Base);
TestKeyword("catch", CSharpKeyword.Catch);
TestKeyword("continue", CSharpKeyword.Continue);
TestKeyword("double", CSharpKeyword.Double);
TestKeyword("for", CSharpKeyword.For);
TestKeyword("in", CSharpKeyword.In);
TestKeyword("lock", CSharpKeyword.Lock);
TestKeyword("object", CSharpKeyword.Object);
TestKeyword("protected", CSharpKeyword.Protected);
TestKeyword("static", CSharpKeyword.Static);
TestKeyword("false", CSharpKeyword.False);
TestKeyword("public", CSharpKeyword.Public);
TestKeyword("sbyte", CSharpKeyword.Sbyte);
TestKeyword("throw", CSharpKeyword.Throw);
TestKeyword("virtual", CSharpKeyword.Virtual);
TestKeyword("decimal", CSharpKeyword.Decimal);
TestKeyword("else", CSharpKeyword.Else);
TestKeyword("operator", CSharpKeyword.Operator);
TestKeyword("string", CSharpKeyword.String);
TestKeyword("ulong", CSharpKeyword.Ulong);
TestKeyword("bool", CSharpKeyword.Bool);
TestKeyword("char", CSharpKeyword.Char);
TestKeyword("default", CSharpKeyword.Default);
TestKeyword("foreach", CSharpKeyword.Foreach);
TestKeyword("long", CSharpKeyword.Long);
TestKeyword("void", CSharpKeyword.Void);
TestKeyword("enum", CSharpKeyword.Enum);
TestKeyword("finally", CSharpKeyword.Finally);
TestKeyword("int", CSharpKeyword.Int);
TestKeyword("out", CSharpKeyword.Out);
TestKeyword("sealed", CSharpKeyword.Sealed);
TestKeyword("true", CSharpKeyword.True);
TestKeyword("goto", CSharpKeyword.Goto);
TestKeyword("unchecked", CSharpKeyword.Unchecked);
TestKeyword("interface", CSharpKeyword.Interface);
TestKeyword("break", CSharpKeyword.Break);
TestKeyword("checked", CSharpKeyword.Checked);
TestKeyword("namespace", CSharpKeyword.Namespace);
TestKeyword("when", CSharpKeyword.When);
}
private void TestKeyword(string keyword, CSharpKeyword keywordType)
{
TestTokenizer(keyword, new CSharpSymbol(0, 0, 0, keyword, CSharpSymbolType.Keyword) { Keyword = keywordType });
}
}
}

View File

@ -0,0 +1,285 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizerLiteralTest : CSharpTokenizerTestBase
{
[Fact]
public void Simple_Integer_Literal_Is_Recognized()
{
TestSingleToken("01189998819991197253", CSharpSymbolType.IntegerLiteral);
}
[Fact]
public void Integer_Type_Suffix_Is_Recognized()
{
TestSingleToken("42U", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42u", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42L", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42l", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42UL", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42Ul", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42uL", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42ul", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42LU", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42Lu", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42lU", CSharpSymbolType.IntegerLiteral);
TestSingleToken("42lu", CSharpSymbolType.IntegerLiteral);
}
[Fact]
public void Trailing_Letter_Is_Not_Part_Of_Integer_Literal_If_Not_Type_Sufix()
{
TestTokenizer("42a", new CSharpSymbol(0, 0, 0, "42", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
}
[Fact]
public void Simple_Hex_Literal_Is_Recognized()
{
TestSingleToken("0x0123456789ABCDEF", CSharpSymbolType.IntegerLiteral);
}
[Fact]
public void Integer_Type_Suffix_Is_Recognized_In_Hex_Literal()
{
TestSingleToken("0xDEADBEEFU", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFu", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFL", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFl", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFUL", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFUl", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFuL", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFul", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFLU", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFLu", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFlU", CSharpSymbolType.IntegerLiteral);
TestSingleToken("0xDEADBEEFlu", CSharpSymbolType.IntegerLiteral);
}
[Fact]
public void Trailing_Letter_Is_Not_Part_Of_Hex_Literal_If_Not_Type_Sufix()
{
TestTokenizer("0xDEADBEEFz", new CSharpSymbol(0, 0, 0, "0xDEADBEEF", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
}
[Fact]
public void Dot_Followed_By_Non_Digit_Is_Not_Part_Of_Real_Literal()
{
TestTokenizer("3.a", new CSharpSymbol(0, 0, 0, "3", CSharpSymbolType.IntegerLiteral), IgnoreRemaining);
}
[Fact]
public void Simple_Real_Literal_Is_Recognized()
{
TestTokenizer("3.14159", new CSharpSymbol(0, 0, 0, "3.14159", CSharpSymbolType.RealLiteral));
}
[Fact]
public void Real_Literal_Between_Zero_And_One_Is_Recognized()
{
TestTokenizer(".14159", new CSharpSymbol(0, 0, 0, ".14159", CSharpSymbolType.RealLiteral));
}
[Fact]
public void Integer_With_Real_Type_Suffix_Is_Recognized()
{
TestSingleToken("42F", CSharpSymbolType.RealLiteral);
TestSingleToken("42f", CSharpSymbolType.RealLiteral);
TestSingleToken("42D", CSharpSymbolType.RealLiteral);
TestSingleToken("42d", CSharpSymbolType.RealLiteral);
TestSingleToken("42M", CSharpSymbolType.RealLiteral);
TestSingleToken("42m", CSharpSymbolType.RealLiteral);
}
[Fact]
public void Integer_With_Exponent_Is_Recognized()
{
TestSingleToken("1e10", CSharpSymbolType.RealLiteral);
TestSingleToken("1E10", CSharpSymbolType.RealLiteral);
TestSingleToken("1e+10", CSharpSymbolType.RealLiteral);
TestSingleToken("1E+10", CSharpSymbolType.RealLiteral);
TestSingleToken("1e-10", CSharpSymbolType.RealLiteral);
TestSingleToken("1E-10", CSharpSymbolType.RealLiteral);
}
[Fact]
public void Real_Number_With_Type_Suffix_Is_Recognized()
{
TestSingleToken("3.14F", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14f", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14D", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14d", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14M", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14m", CSharpSymbolType.RealLiteral);
}
[Fact]
public void Real_Number_With_Exponent_Is_Recognized()
{
TestSingleToken("3.14E10", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14e10", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14E+10", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14e+10", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14E-10", CSharpSymbolType.RealLiteral);
TestSingleToken("3.14e-10", CSharpSymbolType.RealLiteral);
}
[Fact]
public void Real_Number_With_Exponent_And_Type_Suffix_Is_Recognized()
{
TestSingleToken("3.14E+10F", CSharpSymbolType.RealLiteral);
}
[Fact]
public void Single_Character_Literal_Is_Recognized()
{
TestSingleToken("'f'", CSharpSymbolType.CharacterLiteral);
}
[Fact]
public void Multi_Character_Literal_Is_Recognized()
{
TestSingleToken("'foo'", CSharpSymbolType.CharacterLiteral);
}
[Fact]
public void Character_Literal_Is_Terminated_By_EOF_If_Unterminated()
{
TestSingleToken("'foo bar", CSharpSymbolType.CharacterLiteral);
}
[Fact]
public void Character_Literal_Not_Terminated_By_Escaped_Quote()
{
TestSingleToken("'foo\\'bar'", CSharpSymbolType.CharacterLiteral);
}
[Fact]
public void Character_Literal_Is_Terminated_By_EOL_If_Unterminated()
{
TestTokenizer("'foo\n", new CSharpSymbol(0, 0, 0, "'foo", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void Character_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash()
{
TestTokenizer("'foo\\\n", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void Character_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
{
TestTokenizer("'foo\\\nflarg", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void Character_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash()
{
TestTokenizer("'foo\\" + Environment.NewLine, new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void Character_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
{
TestTokenizer($"'foo\\{Environment.NewLine}flarg", new CSharpSymbol(0, 0, 0, "'foo\\", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void Character_Literal_Allows_Escaped_Escape()
{
TestTokenizer("'foo\\\\'blah", new CSharpSymbol(0, 0, 0, "'foo\\\\'", CSharpSymbolType.CharacterLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Is_Recognized()
{
TestSingleToken("\"foo\"", CSharpSymbolType.StringLiteral);
}
[Fact]
public void String_Literal_Is_Terminated_By_EOF_If_Unterminated()
{
TestSingleToken("\"foo bar", CSharpSymbolType.StringLiteral);
}
[Fact]
public void String_Literal_Not_Terminated_By_Escaped_Quote()
{
TestSingleToken("\"foo\\\"bar\"", CSharpSymbolType.StringLiteral);
}
[Fact]
public void String_Literal_Is_Terminated_By_EOL_If_Unterminated()
{
TestTokenizer("\"foo\n", new CSharpSymbol(0, 0, 0, "\"foo", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash()
{
TestTokenizer("\"foo\\\n", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Terminated_By_EOL_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
{
TestTokenizer("\"foo\\\nflarg", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash()
{
TestTokenizer("\"foo\\" + Environment.NewLine, new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Terminated_By_CRLF_Even_When_Last_Char_Is_Slash_And_Followed_By_Stuff()
{
TestTokenizer($"\"foo\\{Environment.NewLine}flarg", new CSharpSymbol(0, 0, 0, "\"foo\\", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void String_Literal_Allows_Escaped_Escape()
{
TestTokenizer("\"foo\\\\\"blah", new CSharpSymbol(0, 0, 0, "\"foo\\\\\"", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void Verbatim_String_Literal_Can_Contain_Newlines()
{
TestSingleToken("@\"foo\nbar\nbaz\"", CSharpSymbolType.StringLiteral);
}
[Fact]
public void Verbatim_String_Literal_Not_Terminated_By_Escaped_Double_Quote()
{
TestSingleToken("@\"foo\"\"bar\"", CSharpSymbolType.StringLiteral);
}
[Fact]
public void Verbatim_String_Literal_Is_Terminated_By_Slash_Double_Quote()
{
TestTokenizer("@\"foo\\\"bar\"", new CSharpSymbol(0, 0, 0, "@\"foo\\\"", CSharpSymbolType.StringLiteral), IgnoreRemaining);
}
[Fact]
public void Verbatim_String_Literal_Is_Terminated_By_EOF()
{
TestSingleToken("@\"foo", CSharpSymbolType.StringLiteral);
}
}
}

View File

@ -0,0 +1,296 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizerOperatorsTest : CSharpTokenizerTestBase
{
[Fact]
public void LeftBrace_Is_Recognized()
{
TestSingleToken("{", CSharpSymbolType.LeftBrace);
}
[Fact]
public void Plus_Is_Recognized()
{
TestSingleToken("+", CSharpSymbolType.Plus);
}
[Fact]
public void Assign_Is_Recognized()
{
TestSingleToken("=", CSharpSymbolType.Assign);
}
[Fact]
public void Arrow_Is_Recognized()
{
TestSingleToken("->", CSharpSymbolType.Arrow);
}
[Fact]
public void AndAssign_Is_Recognized()
{
TestSingleToken("&=", CSharpSymbolType.AndAssign);
}
[Fact]
public void RightBrace_Is_Recognized()
{
TestSingleToken("}", CSharpSymbolType.RightBrace);
}
[Fact]
public void Minus_Is_Recognized()
{
TestSingleToken("-", CSharpSymbolType.Minus);
}
[Fact]
public void LessThan_Is_Recognized()
{
TestSingleToken("<", CSharpSymbolType.LessThan);
}
[Fact]
public void Equals_Is_Recognized()
{
TestSingleToken("==", CSharpSymbolType.Equals);
}
[Fact]
public void OrAssign_Is_Recognized()
{
TestSingleToken("|=", CSharpSymbolType.OrAssign);
}
[Fact]
public void LeftBracket_Is_Recognized()
{
TestSingleToken("[", CSharpSymbolType.LeftBracket);
}
[Fact]
public void Star_Is_Recognized()
{
TestSingleToken("*", CSharpSymbolType.Star);
}
[Fact]
public void GreaterThan_Is_Recognized()
{
TestSingleToken(">", CSharpSymbolType.GreaterThan);
}
[Fact]
public void NotEqual_Is_Recognized()
{
TestSingleToken("!=", CSharpSymbolType.NotEqual);
}
[Fact]
public void XorAssign_Is_Recognized()
{
TestSingleToken("^=", CSharpSymbolType.XorAssign);
}
[Fact]
public void RightBracket_Is_Recognized()
{
TestSingleToken("]", CSharpSymbolType.RightBracket);
}
[Fact]
public void Slash_Is_Recognized()
{
TestSingleToken("/", CSharpSymbolType.Slash);
}
[Fact]
public void QuestionMark_Is_Recognized()
{
TestSingleToken("?", CSharpSymbolType.QuestionMark);
}
[Fact]
public void LessThanEqual_Is_Recognized()
{
TestSingleToken("<=", CSharpSymbolType.LessThanEqual);
}
[Fact]
public void LeftShift_Is_Not_Specially_Recognized()
{
TestTokenizer("<<",
new CSharpSymbol(new SourceLocation(0, 0, 0), "<", CSharpSymbolType.LessThan),
new CSharpSymbol(new SourceLocation(1, 0, 1), "<", CSharpSymbolType.LessThan));
}
[Fact]
public void LeftParen_Is_Recognized()
{
TestSingleToken("(", CSharpSymbolType.LeftParenthesis);
}
[Fact]
public void Modulo_Is_Recognized()
{
TestSingleToken("%", CSharpSymbolType.Modulo);
}
[Fact]
public void NullCoalesce_Is_Recognized()
{
TestSingleToken("??", CSharpSymbolType.NullCoalesce);
}
[Fact]
public void GreaterThanEqual_Is_Recognized()
{
TestSingleToken(">=", CSharpSymbolType.GreaterThanEqual);
}
[Fact]
public void EqualGreaterThan_Is_Recognized()
{
TestSingleToken("=>", CSharpSymbolType.GreaterThanEqual);
}
[Fact]
public void RightParen_Is_Recognized()
{
TestSingleToken(")", CSharpSymbolType.RightParenthesis);
}
[Fact]
public void And_Is_Recognized()
{
TestSingleToken("&", CSharpSymbolType.And);
}
[Fact]
public void DoubleColon_Is_Recognized()
{
TestSingleToken("::", CSharpSymbolType.DoubleColon);
}
[Fact]
public void PlusAssign_Is_Recognized()
{
TestSingleToken("+=", CSharpSymbolType.PlusAssign);
}
[Fact]
public void Semicolon_Is_Recognized()
{
TestSingleToken(";", CSharpSymbolType.Semicolon);
}
[Fact]
public void Tilde_Is_Recognized()
{
TestSingleToken("~", CSharpSymbolType.Tilde);
}
[Fact]
public void DoubleOr_Is_Recognized()
{
TestSingleToken("||", CSharpSymbolType.DoubleOr);
}
[Fact]
public void ModuloAssign_Is_Recognized()
{
TestSingleToken("%=", CSharpSymbolType.ModuloAssign);
}
[Fact]
public void Colon_Is_Recognized()
{
TestSingleToken(":", CSharpSymbolType.Colon);
}
[Fact]
public void Not_Is_Recognized()
{
TestSingleToken("!", CSharpSymbolType.Not);
}
[Fact]
public void DoubleAnd_Is_Recognized()
{
TestSingleToken("&&", CSharpSymbolType.DoubleAnd);
}
[Fact]
public void DivideAssign_Is_Recognized()
{
TestSingleToken("/=", CSharpSymbolType.DivideAssign);
}
[Fact]
public void Comma_Is_Recognized()
{
TestSingleToken(",", CSharpSymbolType.Comma);
}
[Fact]
public void Xor_Is_Recognized()
{
TestSingleToken("^", CSharpSymbolType.Xor);
}
[Fact]
public void Decrement_Is_Recognized()
{
TestSingleToken("--", CSharpSymbolType.Decrement);
}
[Fact]
public void MultiplyAssign_Is_Recognized()
{
TestSingleToken("*=", CSharpSymbolType.MultiplyAssign);
}
[Fact]
public void Dot_Is_Recognized()
{
TestSingleToken(".", CSharpSymbolType.Dot);
}
[Fact]
public void Or_Is_Recognized()
{
TestSingleToken("|", CSharpSymbolType.Or);
}
[Fact]
public void Increment_Is_Recognized()
{
TestSingleToken("++", CSharpSymbolType.Increment);
}
[Fact]
public void MinusAssign_Is_Recognized()
{
TestSingleToken("-=", CSharpSymbolType.MinusAssign);
}
[Fact]
public void RightShift_Is_Not_Specially_Recognized()
{
TestTokenizer(">>",
new CSharpSymbol(0, 0, 0, ">", CSharpSymbolType.GreaterThan),
new CSharpSymbol(1, 0, 1, ">", CSharpSymbolType.GreaterThan));
}
[Fact]
public void Hash_Is_Recognized()
{
TestSingleToken("#", CSharpSymbolType.Hash);
}
}
}

View File

@ -0,0 +1,96 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
internal class CSharpTokenizerTest : CSharpTokenizerTestBase
{
[Fact]
public void Next_Returns_Null_When_EOF_Reached()
{
TestTokenizer("");
}
[Fact]
public void Next_Returns_Newline_Token_For_Single_CR()
{
TestTokenizer("\r\ra",
new CSharpSymbol(0, 0, 0, "\r", CSharpSymbolType.NewLine),
new CSharpSymbol(1, 1, 0, "\r", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Newline_Token_For_Single_LF()
{
TestTokenizer("\n\na",
new CSharpSymbol(0, 0, 0, "\n", CSharpSymbolType.NewLine),
new CSharpSymbol(1, 1, 0, "\n", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Newline_Token_For_Single_NEL()
{
// NEL: Unicode "Next Line" U+0085
TestTokenizer("\u0085\u0085a",
new CSharpSymbol(0, 0, 0, "\u0085", CSharpSymbolType.NewLine),
new CSharpSymbol(1, 1, 0, "\u0085", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Newline_Token_For_Single_Line_Separator()
{
// Unicode "Line Separator" U+2028
TestTokenizer("\u2028\u2028a",
new CSharpSymbol(0, 0, 0, "\u2028", CSharpSymbolType.NewLine),
new CSharpSymbol(1, 1, 0, "\u2028", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Newline_Token_For_Single_Paragraph_Separator()
{
// Unicode "Paragraph Separator" U+2029
TestTokenizer("\u2029\u2029a",
new CSharpSymbol(0, 0, 0, "\u2029", CSharpSymbolType.NewLine),
new CSharpSymbol(1, 1, 0, "\u2029", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Single_Newline_Token_For_CRLF()
{
TestTokenizer("\r\n\r\na",
new CSharpSymbol(0, 0, 0, "\r\n", CSharpSymbolType.NewLine),
new CSharpSymbol(2, 1, 0, "\r\n", CSharpSymbolType.NewLine),
IgnoreRemaining);
}
[Fact]
public void Next_Returns_Token_For_Whitespace_Characters()
{
TestTokenizer(" \f\t\u000B \n ",
new CSharpSymbol(0, 0, 0, " \f\t\u000B ", CSharpSymbolType.WhiteSpace),
new CSharpSymbol(5, 0, 5, "\n", CSharpSymbolType.NewLine),
new CSharpSymbol(6, 1, 0, " ", CSharpSymbolType.WhiteSpace));
}
[Fact]
public void Transition_Is_Recognized()
{
TestSingleToken("@", CSharpSymbolType.Transition);
}
[Fact]
public void Transition_Is_Recognized_As_SingleCharacter()
{
TestTokenizer("@(",
new CSharpSymbol(0, 0, 0, "@", CSharpSymbolType.Transition),
new CSharpSymbol(1, 0, 1, "(", CSharpSymbolType.LeftParenthesis));
}
}
}

Some files were not shown because too many files have changed in this diff Show More