From fe5e1b8c71ef46a2e7e8a647d435fa0a7a38d6f0 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 3 Jan 2016 12:13:54 -0800 Subject: [PATCH] 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 --- .../TagHelpers/TagHelperDescriptorResolver.cs | 3 +- .../CodeGenerators/CSharpCodeWriter.cs | 48 +++++-- .../CodeGenerators/LineMapping.cs | 2 +- .../Visitors/CSharpCodeVisitor.cs | 17 ++- .../Parser/CSharpCodeParser.Statements.cs | 27 ++-- .../Parser/CSharpCodeParser.cs | 29 ++-- .../TagHelpers/TagHelperBlockRewriter.cs | 4 +- .../TagHelperDirectiveSpanVisitor.cs | 2 +- .../TagHelpers/TagHelperParseTreeRewriter.cs | 4 +- .../RazorEditorParser.cs | 134 +++++++++++------- 10 files changed, 174 insertions(+), 96 deletions(-) diff --git a/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorResolver.cs b/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorResolver.cs index 895d2e12cd..4ce9231994 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorResolver.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorResolver.cs @@ -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); diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs index 8eb490b44a..980c147e76 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpCodeWriter.cs @@ -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 baseTypes) + public CSharpCodeWritingScope BuildClassDeclaration( + string accessibility, + string name, + IEnumerable baseTypes) { Write(accessibility).Write(" class ").Write(name); @@ -388,9 +395,17 @@ namespace Microsoft.AspNet.Razor.CodeGenerators return BuildConstructor(accessibility, name, Enumerable.Empty>()); } - public CSharpCodeWritingScope BuildConstructor(string accessibility, string name, IEnumerable> parameters) + public CSharpCodeWritingScope BuildConstructor( + string accessibility, + string name, + IEnumerable> 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>()); } - public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name, IEnumerable> parameters) + public CSharpCodeWritingScope BuildMethodDeclaration( + string accessibility, + string returnType, + string name, + IEnumerable> 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); } diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/LineMapping.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/LineMapping.cs index f8dfc4d1cb..39a7e7d2c8 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/LineMapping.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/LineMapping.cs @@ -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); } } } diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpCodeVisitor.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpCodeVisitor.cs index 1556a9ad52..f9965d9e48 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpCodeVisitor.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/Visitors/CSharpCodeVisitor.cs @@ -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); diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs index 953c7f10ae..03dbbb5213 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs @@ -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 '' tag. - if (nextSymbol != null && !nextSymbol.Content.Equals(SyntaxConstants.TextTagName)) + // Put back the whitespace unless it precedes a '' 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)) diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs index 52e353e15b..afcf6aa532 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs @@ -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); diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs index 610c091cca..e6a410b355 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs @@ -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; } diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs index 6a196427de..718bc04981 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperDirectiveSpanVisitor.cs @@ -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 diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs index 413e2ff9ba..f46dd21e05 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperParseTreeRewriter.cs @@ -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) diff --git a/src/Microsoft.AspNet.Razor/RazorEditorParser.cs b/src/Microsoft.AspNet.Razor/RazorEditorParser.cs index a4257bef83..f9140683e2 100644 --- a/src/Microsoft.AspNet.Razor/RazorEditorParser.cs +++ b/src/Microsoft.AspNet.Razor/RazorEditorParser.cs @@ -12,45 +12,65 @@ using Microsoft.AspNet.Razor.Text; namespace Microsoft.AspNet.Razor { /// - /// 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. /// /// + /// /// 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 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. + /// + /// /// 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. + /// + /// User edits document. + /// Editor builds a structure describing the edit and providing a + /// reference to the updated text buffer. + /// Editor calls passing in that change. + /// + /// Parser determines if the change can be simply applied to an existing parse tree node. + /// + /// + /// If it can, the Parser updates its parse tree and returns + /// . + /// If it cannot, the Parser starts a background parse task and returns + /// . + /// + /// + /// NOTE: Additional flags can be applied to the , see that enum for more + /// details. However, the or + /// flags will ALWAYS be present. + /// + /// + /// A change can only be incrementally parsed if a single, unique, (see + /// ) in the syntax tree can be identified as owning the entire change. + /// For example, if a change overlaps with multiple s, the change cannot be parsed incrementally + /// and a full reparse is necessary. A "owns" a change if the change occurs either a) entirely + /// within it's boundaries or b) it is a pure insertion (see ) at the end of a + /// whose can accept the change (see + /// ). + /// + /// + /// When the returns , it updates + /// immediately. However, the editor is expected to update it's own data structures + /// independently. It can use to do this, as soon as the editor returns from + /// , but it should (ideally) have logic for doing so without needing the new + /// tree. + /// + /// + /// When is returned by , a + /// background parse task has already been started. When that task finishes, the + /// event will be fired containing the new generated code, parse tree and a + /// reference to the original that caused the reparse, to allow the editor to resolve the + /// new tree against any changes made since calling . + /// + /// + /// If a call to occurs while a reparse is already in-progress, the reparse + /// is canceled IMMEDIATELY and is returned without attempting to + /// reparse. This means that if a consumer calls , which returns + /// , then calls it again before is + /// fired, it will only receive one event, for the second change. + /// /// public class RazorEditorParser : IDisposable { @@ -61,11 +81,13 @@ namespace Microsoft.AspNet.Razor private Block _currentParseTree; /// - /// 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 can be shared among reparses, but should never be shared between documents. /// - /// The which defines the environment in which the generated code will live. should be set if design-time code mappings are desired - /// The physical path to use in line pragmas + /// The which defines the environment in which the generated + /// code will live. should be set if design-time behavior is + /// desired. + /// The physical path to use in line pragmas. public RazorEditorParser(RazorEngineHost host, string sourceFileName) { if (host == null) @@ -86,7 +108,7 @@ namespace Microsoft.AspNet.Razor } /// - /// Event fired when a full reparse of the document completes + /// Event fired when a full reparse of the document completes. /// public event EventHandler DocumentParseComplete; @@ -112,19 +134,20 @@ namespace Microsoft.AspNet.Razor } /// - /// 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. /// /// /// 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. /// - /// The change to apply to the parse tree - /// A PartialParseResult value indicating the result of the incremental parse + /// The change to apply to the parse tree. + /// A value indicating the result of the incremental parse. 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; } /// - /// 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. /// public void Dispose() { @@ -235,6 +260,7 @@ namespace Microsoft.AspNet.Razor _lastAutoCompleteSpan = null; } } + return result; }