Make Razor codegen use invariant culture
- #557 - use `int.ToString(CultureInfo.InvariantCulture)` consistently in `CSharpCodeWriter` / `CSharpCodeVisitor` - correct `string` operations to use explicit `StringComparison.Ordinal` - improve `RazorEditorParser` doc comments nits: - remove one-off use of `CurrentUICulture` in `LineMapping` debug code (`ToString()` implementation) - clean a bit of #YOLO wrapping and long lines
This commit is contained in:
parent
37eca627dd
commit
fe5e1b8c71
|
|
@ -276,7 +276,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
var trimmedAssemblyName = lookupStrings[1].Trim();
|
||||
|
||||
// + 1 is for the comma separator in the lookup text.
|
||||
var assemblyNameIndex = lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName);
|
||||
var assemblyNameIndex =
|
||||
lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName, StringComparison.Ordinal);
|
||||
var assemblyNamePrefix = directiveDescriptor.DirectiveText.Substring(0, assemblyNameIndex);
|
||||
var assemblyNameLocation = SourceLocation.Advance(directiveDescriptor.Location, assemblyNamePrefix);
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
{
|
||||
WriteStringLiteral(value.Value);
|
||||
WriteParameterSeparator();
|
||||
Write(value.Location.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
|
||||
Write(value.Location.AbsoluteIndex.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
@ -144,7 +144,8 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
|
||||
public CSharpCodeWriter WriteUsing(string name, bool endLine)
|
||||
{
|
||||
Write(string.Format("using {0}", name));
|
||||
Write("using ");
|
||||
Write(name);
|
||||
|
||||
if (endLine)
|
||||
{
|
||||
|
|
@ -225,6 +226,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
{
|
||||
return WriteEndMethodInvocation(endLine: true);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteEndMethodInvocation(bool endLine)
|
||||
{
|
||||
Write(")");
|
||||
|
|
@ -300,7 +302,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
|
||||
public CSharpCodeWriter WriteMethodInvocation(string methodName, bool endLine, params string[] parameters)
|
||||
{
|
||||
return WriteStartMethodInvocation(methodName).Write(string.Join(", ", parameters)).WriteEndMethodInvocation(endLine);
|
||||
return WriteStartMethodInvocation(methodName)
|
||||
.Write(string.Join(", ", parameters))
|
||||
.WriteEndMethodInvocation(endLine);
|
||||
}
|
||||
|
||||
public CSharpDisableWarningScope BuildDisableWarningScope(int warning)
|
||||
|
|
@ -363,7 +367,10 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
return BuildClassDeclaration(accessibility, name, new string[] { baseType });
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name, IEnumerable<string> baseTypes)
|
||||
public CSharpCodeWritingScope BuildClassDeclaration(
|
||||
string accessibility,
|
||||
string name,
|
||||
IEnumerable<string> baseTypes)
|
||||
{
|
||||
Write(accessibility).Write(" class ").Write(name);
|
||||
|
||||
|
|
@ -388,9 +395,17 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
return BuildConstructor(accessibility, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildConstructor(string accessibility, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
public CSharpCodeWritingScope BuildConstructor(
|
||||
string accessibility,
|
||||
string name,
|
||||
IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
Write(accessibility).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||
Write(accessibility)
|
||||
.Write(" ")
|
||||
.Write(name)
|
||||
.Write("(")
|
||||
.Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value)))
|
||||
.WriteLine(")");
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
|
@ -400,15 +415,28 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
return BuildMethodDeclaration(accessibility, returnType, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
public CSharpCodeWritingScope BuildMethodDeclaration(
|
||||
string accessibility,
|
||||
string returnType,
|
||||
string name,
|
||||
IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
Write(accessibility).Write(" ").Write(returnType).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||
Write(accessibility)
|
||||
.Write(" ")
|
||||
.Write(returnType)
|
||||
.Write(" ")
|
||||
.Write(name)
|
||||
.Write("(")
|
||||
.Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value)))
|
||||
.WriteLine(")");
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
// TODO: Do I need to look at the document content to determine its mapping length?
|
||||
public CSharpLineMappingWriter BuildLineMapping(SourceLocation documentLocation, int contentLength, string sourceFilename)
|
||||
public CSharpLineMappingWriter BuildLineMapping(
|
||||
SourceLocation documentLocation,
|
||||
int contentLength,
|
||||
string sourceFilename)
|
||||
{
|
||||
return new CSharpLineMappingWriter(this, documentLocation, contentLength, sourceFilename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentUICulture, "{0} -> {1}", DocumentLocation, GeneratedLocation);
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} -> {1}", DocumentLocation, GeneratedLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
|
|
@ -257,9 +256,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors
|
|||
|
||||
Writer
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.WriteBooleanLiteral(value: false)
|
||||
.WriteEndMethodInvocation();
|
||||
|
|
@ -281,9 +280,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors
|
|||
Writer
|
||||
.WriteEndMethodInvocation(false)
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.WriteBooleanLiteral(false)
|
||||
.WriteEndMethodInvocation();
|
||||
|
|
@ -334,9 +333,9 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors
|
|||
|
||||
Writer
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.ValueLocation.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.ValueLocation.AbsoluteIndex.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.WriteBooleanLiteral(false)
|
||||
.WriteEndMethodInvocation();
|
||||
|
|
@ -346,7 +345,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors
|
|||
Writer
|
||||
.WriteLocationTaggedString(chunk.Value)
|
||||
.WriteParameterSeparator()
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(chunk.Association.Length.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.WriteBooleanLiteral(true)
|
||||
.WriteEndMethodInvocation();
|
||||
|
|
@ -382,7 +381,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators.Visitors
|
|||
.WriteParameterSeparator()
|
||||
.WriteLocationTaggedString(chunk.Suffix)
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeCount.ToString(CultureInfo.CurrentCulture))
|
||||
.Write(attributeCount.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
Accept(chunk.Children);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,13 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
|
||||
private void SetUpKeywords()
|
||||
{
|
||||
MapKeywords(ConditionalBlock, CSharpKeyword.For, CSharpKeyword.Foreach, CSharpKeyword.While, CSharpKeyword.Switch, CSharpKeyword.Lock);
|
||||
MapKeywords(
|
||||
ConditionalBlock,
|
||||
CSharpKeyword.For,
|
||||
CSharpKeyword.Foreach,
|
||||
CSharpKeyword.While,
|
||||
CSharpKeyword.Switch,
|
||||
CSharpKeyword.Lock);
|
||||
MapKeywords(CaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
|
||||
MapKeywords(IfStatement, CSharpKeyword.If);
|
||||
MapKeywords(TryStatement, CSharpKeyword.Try);
|
||||
|
|
@ -443,7 +449,8 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
|
||||
// Accept whitespace but always keep the last whitespace node so we can put it back if necessary
|
||||
var lastWhitespace = AcceptWhiteSpaceInLines();
|
||||
Debug.Assert(lastWhitespace == null || (lastWhitespace.Start.AbsoluteIndex + lastWhitespace.Content.Length == CurrentLocation.AbsoluteIndex));
|
||||
Debug.Assert(lastWhitespace == null ||
|
||||
(lastWhitespace.Start.AbsoluteIndex + lastWhitespace.Content.Length == CurrentLocation.AbsoluteIndex));
|
||||
|
||||
if (EndOfFile)
|
||||
{
|
||||
|
|
@ -459,8 +466,8 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
|
||||
var isSingleLineMarkup = type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.Colon);
|
||||
var isMarkup = isSingleLineMarkup ||
|
||||
type == CSharpSymbolType.LessThan ||
|
||||
(type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.LessThan));
|
||||
type == CSharpSymbolType.LessThan ||
|
||||
(type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.LessThan));
|
||||
|
||||
if (Context.DesignTimeMode || !isMarkup)
|
||||
{
|
||||
|
|
@ -477,8 +484,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
// MARKUP owns whitespace EXCEPT in DesignTimeMode.
|
||||
PutCurrentBack();
|
||||
|
||||
// Don't putback the whitespace if it precedes a '<text>' tag.
|
||||
if (nextSymbol != null && !nextSymbol.Content.Equals(SyntaxConstants.TextTagName))
|
||||
// Put back the whitespace unless it precedes a '<text>' tag.
|
||||
if (nextSymbol != null &&
|
||||
!string.Equals(nextSymbol.Content, SyntaxConstants.TextTagName, StringComparison.Ordinal))
|
||||
{
|
||||
PutBack(lastWhitespace);
|
||||
}
|
||||
|
|
@ -496,7 +504,8 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
|
||||
// Markup block
|
||||
Output(SpanKind.Code);
|
||||
if (Context.DesignTimeMode && CurrentSymbol != null && (CurrentSymbol.Type == CSharpSymbolType.LessThan || CurrentSymbol.Type == CSharpSymbolType.Transition))
|
||||
if (Context.DesignTimeMode && CurrentSymbol != null &&
|
||||
(CurrentSymbol.Type == CSharpSymbolType.LessThan || CurrentSymbol.Type == CSharpSymbolType.Transition))
|
||||
{
|
||||
PutCurrentBack();
|
||||
}
|
||||
|
|
@ -600,7 +609,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
sym.Type != CSharpSymbolType.LeftParenthesis &&
|
||||
sym.Type != CSharpSymbolType.LeftBracket &&
|
||||
sym.Type != CSharpSymbolType.RightBrace);
|
||||
if (At(CSharpSymbolType.LeftBrace) || At(CSharpSymbolType.LeftParenthesis) || At(CSharpSymbolType.LeftBracket))
|
||||
if (At(CSharpSymbolType.LeftBrace) ||
|
||||
At(CSharpSymbolType.LeftParenthesis) ||
|
||||
At(CSharpSymbolType.LeftBracket))
|
||||
{
|
||||
Accept(read);
|
||||
if (Balance(BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNet.Razor.Editor;
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Editor;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Tokenizer;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
|
@ -103,12 +103,16 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
[Conditional("DEBUG")]
|
||||
internal void Assert(CSharpKeyword expectedKeyword)
|
||||
{
|
||||
Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword && CurrentSymbol.Keyword.HasValue && CurrentSymbol.Keyword.Value == expectedKeyword);
|
||||
Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword &&
|
||||
CurrentSymbol.Keyword.HasValue &&
|
||||
CurrentSymbol.Keyword.Value == expectedKeyword);
|
||||
}
|
||||
|
||||
protected internal bool At(CSharpKeyword keyword)
|
||||
{
|
||||
return At(CSharpSymbolType.Keyword) && CurrentSymbol.Keyword.HasValue && CurrentSymbol.Keyword.Value == keyword;
|
||||
return At(CSharpSymbolType.Keyword) &&
|
||||
CurrentSymbol.Keyword.HasValue &&
|
||||
CurrentSymbol.Keyword.Value == keyword;
|
||||
}
|
||||
|
||||
protected internal bool AcceptIf(CSharpKeyword keyword)
|
||||
|
|
@ -145,7 +149,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
|
||||
|
||||
var current = CurrentSymbol;
|
||||
if (At(CSharpSymbolType.StringLiteral) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == SyntaxConstants.TransitionCharacter)
|
||||
if (At(CSharpSymbolType.StringLiteral) &&
|
||||
CurrentSymbol.Content.Length > 0 &&
|
||||
CurrentSymbol.Content[0] == SyntaxConstants.TransitionCharacter)
|
||||
{
|
||||
var split = Language.SplitSymbol(CurrentSymbol, 1, CSharpSymbolType.Transition);
|
||||
current = split.Item1;
|
||||
|
|
@ -222,11 +228,15 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
}
|
||||
else
|
||||
{
|
||||
if (CurrentSymbol.Content.Equals(SyntaxConstants.CSharp.HelperKeyword))
|
||||
if (string.Equals(
|
||||
CurrentSymbol.Content,
|
||||
SyntaxConstants.CSharp.HelperKeyword,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
Context.OnError(
|
||||
CurrentLocation,
|
||||
RazorResources.FormatParseError_HelperDirectiveNotAvailable(SyntaxConstants.CSharp.HelperKeyword),
|
||||
RazorResources.FormatParseError_HelperDirectiveNotAvailable(
|
||||
SyntaxConstants.CSharp.HelperKeyword),
|
||||
CurrentSymbol.Content.Length);
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +390,8 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
{
|
||||
if (!EndOfFile)
|
||||
{
|
||||
if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis || CurrentSymbol.Type == CSharpSymbolType.LeftBracket)
|
||||
if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis ||
|
||||
CurrentSymbol.Type == CSharpSymbolType.LeftBracket)
|
||||
{
|
||||
// If we end within "(", whitespace is fine
|
||||
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
|
||||
|
|
@ -541,7 +552,9 @@ namespace Microsoft.AspNet.Razor.Parser
|
|||
using (PushSpanConfig(ConfigureExplicitExpressionSpan))
|
||||
{
|
||||
var success = Balance(
|
||||
BalancingModes.BacktrackOnFailure | BalancingModes.NoErrorOnFailure | BalancingModes.AllowCommentsAndTemplates,
|
||||
BalancingModes.BacktrackOnFailure |
|
||||
BalancingModes.NoErrorOnFailure |
|
||||
BalancingModes.AllowCommentsAndTemplates,
|
||||
CSharpSymbolType.LeftParenthesis,
|
||||
CSharpSymbolType.RightParenthesis,
|
||||
block.Start);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
var childSpan = beginTagBlock.FindLastDescendentSpan();
|
||||
|
||||
// Self-closing tags are always valid despite descriptors[X].TagStructure.
|
||||
if (childSpan?.Content.EndsWith("/>") ?? false)
|
||||
if (childSpan?.Content.EndsWith("/>", StringComparison.Ordinal) ?? false)
|
||||
{
|
||||
return TagMode.SelfClosing;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
}
|
||||
|
||||
var directiveText = span.Content.Trim();
|
||||
var startOffset = span.Content.IndexOf(directiveText);
|
||||
var startOffset = span.Content.IndexOf(directiveText, StringComparison.Ordinal);
|
||||
var offsetContent = span.Content.Substring(0, startOffset);
|
||||
var offsetTextLocation = SourceLocation.Advance(span.Start, offsetContent);
|
||||
var directiveDescriptor = new TagHelperDirectiveDescriptor
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||
|
|
@ -663,7 +663,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
var childSpan = childBlock.FindLastDescendentSpan();
|
||||
|
||||
return childSpan?.Content.EndsWith("/>") ?? false;
|
||||
return childSpan?.Content.EndsWith("/>", StringComparison.Ordinal) ?? false;
|
||||
}
|
||||
|
||||
private void PushTrackerStack(TagBlockTracker tracker)
|
||||
|
|
|
|||
|
|
@ -12,45 +12,65 @@ using Microsoft.AspNet.Razor.Text;
|
|||
namespace Microsoft.AspNet.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Parser used by editors to avoid reparsing the entire document on each text change
|
||||
/// Parser used by editors to avoid reparsing the entire document on each text change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This parser is designed to allow editors to avoid having to worry about incremental parsing.
|
||||
/// The CheckForStructureChanges method can be called with every change made by a user in an editor and
|
||||
/// the parser will provide a result indicating if it was able to incrementally reparse the document.
|
||||
///
|
||||
/// The <see cref="CheckForStructureChanges"/> method can be called with every change made by a user in an editor
|
||||
/// and the parser will provide a result indicating if it was able to incrementally apply the change.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The general workflow for editors with this parser is:
|
||||
/// 0. User edits document
|
||||
/// 1. Editor builds TextChange structure describing the edit and providing a reference to the _updated_ text buffer
|
||||
/// 2. Editor calls CheckForStructureChanges passing in that change.
|
||||
/// 3. Parser determines if the change can be simply applied to an existing parse tree node
|
||||
/// a. If it can, the Parser updates its parse tree and returns PartialParseResult.Accepted
|
||||
/// b. If it can not, the Parser starts a background parse task and return PartialParseResult.Rejected
|
||||
/// NOTE: Additional flags can be applied to the PartialParseResult, see that enum for more details. However,
|
||||
/// the Accepted or Rejected flags will ALWAYS be present
|
||||
///
|
||||
/// A change can only be incrementally parsed if a single, unique, Span (see Microsoft.AspNet.Razor.Parser.SyntaxTree) in the syntax tree can
|
||||
/// be identified as owning the entire change. For example, if a change overlaps with multiple spans, the change cannot be
|
||||
/// parsed incrementally and a full reparse is necessary. A Span "owns" a change if the change occurs either a) entirely
|
||||
/// within it's boundaries or b) it is a pure insertion (see TextChange) at the end of a Span whose CanGrow flag (see Span) is
|
||||
/// true.
|
||||
///
|
||||
/// Even if a single unique Span owner can be identified, it's possible the edit will cause the Span to split or merge with other
|
||||
/// Spans, in which case, a full reparse is necessary to identify the extent of the changes to the tree.
|
||||
///
|
||||
/// When the RazorEditorParser returns Accepted, it updates CurrentParseTree immediately. However, the editor is expected to
|
||||
/// update it's own data structures independently. It can use CurrentParseTree to do this, as soon as the editor returns from
|
||||
/// CheckForStructureChanges, but it should (ideally) have logic for doing so without needing the new tree.
|
||||
///
|
||||
/// When Rejected is returned by CheckForStructureChanges, a background parse task has _already_ been started. When that task
|
||||
/// finishes, the DocumentStructureChanged event will be fired containing the new generated code, parse tree and a reference to
|
||||
/// the original TextChange that caused the reparse, to allow the editor to resolve the new tree against any changes made since
|
||||
/// calling CheckForStructureChanges.
|
||||
///
|
||||
/// If a call to CheckForStructureChanges occurs while a reparse is already in-progress, the reparse is cancelled IMMEDIATELY
|
||||
/// and Rejected is returned without attempting to reparse. This means that if a conusmer calls CheckForStructureChanges, which
|
||||
/// returns Rejected, then calls it again before DocumentParseComplete is fired, it will only recieve one DocumentParseComplete
|
||||
/// event, for the second change.
|
||||
/// <list type="number">
|
||||
/// <item><description>User edits document.</description></item>
|
||||
/// <item><description>Editor builds a <see cref="TextChange"/> structure describing the edit and providing a
|
||||
/// reference to the <em>updated</em> text buffer.</description></item>
|
||||
/// <item><description>Editor calls <see cref="CheckForStructureChanges"/> passing in that change.
|
||||
/// </description></item>
|
||||
/// <item><description>Parser determines if the change can be simply applied to an existing parse tree node.
|
||||
/// </description></item>
|
||||
/// <list type="number">
|
||||
/// <item><description>If it can, the Parser updates its parse tree and returns
|
||||
/// <see cref="PartialParseResult.Accepted"/>.</description></item>
|
||||
/// <item><description>If it cannot, the Parser starts a background parse task and returns
|
||||
/// <see cref="PartialParseResult.Rejected"/>.</description></item>
|
||||
/// </list>
|
||||
/// </list>
|
||||
/// NOTE: Additional flags can be applied to the <see cref="PartialParseResult"/>, see that <c>enum</c> for more
|
||||
/// details. However, the <see cref="PartialParseResult.Accepted"/> or <see cref="PartialParseResult.Rejected"/>
|
||||
/// flags will ALWAYS be present.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A change can only be incrementally parsed if a single, unique, <see cref="Span"/> (see
|
||||
/// <see cref="Parser.SyntaxTree"/>) in the syntax tree can be identified as owning the entire change.
|
||||
/// For example, if a change overlaps with multiple <see cref="Span"/>s, the change cannot be parsed incrementally
|
||||
/// and a full reparse is necessary. A <see cref="Span"/> "owns" a change if the change occurs either a) entirely
|
||||
/// within it's boundaries or b) it is a pure insertion (see <see cref="TextChange"/>) at the end of a
|
||||
/// <see cref="Span"/> whose <see cref="Span.EditHandler"/> can accept the change (see
|
||||
/// <see cref="SpanEditHandler.CanAcceptChange"/>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When the <see cref="RazorEditorParser"/> returns <see cref="PartialParseResult.Accepted"/>, it updates
|
||||
/// <see cref="CurrentParseTree"/> immediately. However, the editor is expected to update it's own data structures
|
||||
/// independently. It can use <see cref="CurrentParseTree"/> to do this, as soon as the editor returns from
|
||||
/// <see cref="CheckForStructureChanges"/>, but it should (ideally) have logic for doing so without needing the new
|
||||
/// tree.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When <see cref="PartialParseResult.Rejected"/> is returned by <see cref="CheckForStructureChanges"/>, a
|
||||
/// background parse task has <em>already</em> been started. When that task finishes, the
|
||||
/// <see cref="DocumentParseComplete"/> event will be fired containing the new generated code, parse tree and a
|
||||
/// reference to the original <see cref="TextChange"/> that caused the reparse, to allow the editor to resolve the
|
||||
/// new tree against any changes made since calling <see cref="CheckForStructureChanges"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If a call to <see cref="CheckForStructureChanges"/> occurs while a reparse is already in-progress, the reparse
|
||||
/// is canceled IMMEDIATELY and <see cref="PartialParseResult.Rejected"/> is returned without attempting to
|
||||
/// reparse. This means that if a consumer calls <see cref="CheckForStructureChanges"/>, which returns
|
||||
/// <see cref="PartialParseResult.Rejected"/>, then calls it again before <see cref="DocumentParseComplete"/> is
|
||||
/// fired, it will only receive one <see cref="DocumentParseComplete"/> event, for the second change.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class RazorEditorParser : IDisposable
|
||||
{
|
||||
|
|
@ -61,11 +81,13 @@ namespace Microsoft.AspNet.Razor
|
|||
private Block _currentParseTree;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the editor parser. One instance should be used per active editor. This
|
||||
/// instance _can_ be shared among reparses, but should _never_ be shared between documents.
|
||||
/// Constructs the editor parser. One instance should be used per active editor. This
|
||||
/// instance <em>can</em> be shared among reparses, but should <em>never</em> be shared between documents.
|
||||
/// </summary>
|
||||
/// <param name="host">The <see cref="RazorEngineHost"/> which defines the environment in which the generated code will live. <see cref="F:RazorEngineHost.DesignTimeMode"/> should be set if design-time code mappings are desired</param>
|
||||
/// <param name="sourceFileName">The physical path to use in line pragmas</param>
|
||||
/// <param name="host">The <see cref="RazorEngineHost"/> which defines the environment in which the generated
|
||||
/// code will live. <see cref="RazorEngineHost.DesignTimeMode"/> should be set if design-time behavior is
|
||||
/// desired.</param>
|
||||
/// <param name="sourceFileName">The physical path to use in line pragmas.</param>
|
||||
public RazorEditorParser(RazorEngineHost host, string sourceFileName)
|
||||
{
|
||||
if (host == null)
|
||||
|
|
@ -86,7 +108,7 @@ namespace Microsoft.AspNet.Razor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a full reparse of the document completes
|
||||
/// Event fired when a full reparse of the document completes.
|
||||
/// </summary>
|
||||
public event EventHandler<DocumentParseCompleteEventArgs> DocumentParseComplete;
|
||||
|
||||
|
|
@ -112,19 +134,20 @@ namespace Microsoft.AspNet.Razor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a change will cause a structural change to the document and if not, applies it to the existing tree.
|
||||
/// If a structural change would occur, automatically starts a reparse
|
||||
/// Determines if a change will cause a structural change to the document and if not, applies it to the
|
||||
/// existing tree. If a structural change would occur, automatically starts a reparse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: The initial incremental parsing check and actual incremental parsing (if possible) occurs
|
||||
/// on the callers thread. However, if a full reparse is needed, this occurs on a background thread.
|
||||
/// on the caller's thread. However, if a full reparse is needed, this occurs on a background thread.
|
||||
/// </remarks>
|
||||
/// <param name="change">The change to apply to the parse tree</param>
|
||||
/// <returns>A PartialParseResult value indicating the result of the incremental parse</returns>
|
||||
/// <param name="change">The change to apply to the parse tree.</param>
|
||||
/// <returns>A <see cref="PartialParseResult"/> value indicating the result of the incremental parse.</returns>
|
||||
public virtual PartialParseResult CheckForStructureChanges(TextChange change)
|
||||
{
|
||||
// Validate the change
|
||||
long? elapsedMs = null;
|
||||
|
||||
#if EDITOR_TRACING
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
|
@ -132,9 +155,9 @@ namespace Microsoft.AspNet.Razor
|
|||
RazorEditorTrace.TraceLine(RazorResources.FormatTrace_EditorReceivedChange(Path.GetFileName(FileName), change));
|
||||
if (change.NewBuffer == null)
|
||||
{
|
||||
throw new ArgumentException(RazorResources.FormatStructure_Member_CannotBeNull(
|
||||
nameof(change.NewBuffer),
|
||||
nameof(TextChange)), nameof(change));
|
||||
throw new ArgumentException(
|
||||
RazorResources.FormatStructure_Member_CannotBeNull(nameof(change.NewBuffer), nameof(TextChange)),
|
||||
nameof(change));
|
||||
}
|
||||
|
||||
var result = PartialParseResult.Rejected;
|
||||
|
|
@ -168,16 +191,18 @@ namespace Microsoft.AspNet.Razor
|
|||
elapsedMs = sw.ElapsedMilliseconds;
|
||||
sw.Reset();
|
||||
#endif
|
||||
RazorEditorTrace.TraceLine(
|
||||
RazorResources.FormatTrace_EditorProcessedChange(
|
||||
Path.GetFileName(FileName),
|
||||
changeString, elapsedMs.HasValue ? elapsedMs.Value.ToString(CultureInfo.InvariantCulture) : "?",
|
||||
result.ToString()));
|
||||
|
||||
RazorEditorTrace.TraceLine(RazorResources.FormatTrace_EditorProcessedChange(
|
||||
Path.GetFileName(FileName),
|
||||
changeString,
|
||||
elapsedMs.HasValue ? elapsedMs.Value.ToString(CultureInfo.InvariantCulture) : "?",
|
||||
result.ToString()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of this parser. Should be called when the editor window is closed and the document is unloaded.
|
||||
/// Disposes of this parser. Should be called when the editor window is closed and the document is unloaded.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -235,6 +260,7 @@ namespace Microsoft.AspNet.Razor
|
|||
_lastAutoCompleteSpan = null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue