Add legacy parser
This commit is contained in:
parent
6b9b75841f
commit
d06e5b6002
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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><<character literal>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_Comment" xml:space="preserve">
|
||||
<value><<comment>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_Identifier" xml:space="preserve">
|
||||
<value><<identifier>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_IntegerLiteral" xml:space="preserve">
|
||||
<value><<integer literal>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_Keyword" xml:space="preserve">
|
||||
<value><<keyword>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_Newline" xml:space="preserve">
|
||||
<value><<newline sequence>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_RealLiteral" xml:space="preserve">
|
||||
<value><<real literal>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_StringLiteral" xml:space="preserve">
|
||||
<value><<string literal>></value>
|
||||
</data>
|
||||
<data name="CSharpSymbol_Whitespace" xml:space="preserve">
|
||||
<value><<white space>></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><<newline sequence>></value>
|
||||
</data>
|
||||
<data name="HtmlSymbol_RazorComment" xml:space="preserve">
|
||||
<value><<razor comment>></value>
|
||||
</data>
|
||||
<data name="HtmlSymbol_Text" xml:space="preserve">
|
||||
<value><<text>></value>
|
||||
</data>
|
||||
<data name="HtmlSymbol_WhiteSpace" xml:space="preserve">
|
||||
<value><<white space>></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) {
|
||||
<p>Hello, @user!</p>
|
||||
}</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 (@<p>Content</p>) 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 "<br>". Instead use self-closing tags like "<br/>".</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 {
|
||||
<!-- Markup and text goes here -->
|
||||
}</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)
|
||||
<p>Hello, @user</p>
|
||||
|
||||
Instead, wrap the contents of the block in "{{}}":
|
||||
|
||||
@if(isLoggedIn) {{
|
||||
<p>Hello, @user</p>
|
||||
}}</value>
|
||||
<comment>{0} is only ever a single character</comment>
|
||||
</data>
|
||||
<data name="ParseError_TextTagCannotContainAttributes" xml:space="preserve">
|
||||
<value>"<text>" and "</text>" 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 ("<br />") or have matching end tags ("<p>Hello</p>"). If you intended to display a "<" character, use the "&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><<unknown>></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>
|
||||
1162
src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs
generated
Normal file
1162
src/Microsoft.AspNetCore.Razor.Evolution/Properties/LegacyResources.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>"))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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("foobar", new CSharpSymbol(0, 0, 0, "foobar", 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue