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:
Doug Bunting 2016-01-03 12:13:54 -08:00
parent 37eca627dd
commit fe5e1b8c71
10 changed files with 174 additions and 96 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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))

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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;
}