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