From 0688cd3ef73ea50999e7a95aa53a12e7e03f3e93 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 19 May 2017 15:14:59 -0700 Subject: [PATCH] Log errors if directives do not start at beginning of line. - Updated tests to validate expectations. - Added two additional tests to validate extensible directives and built-in directives get start at line verification. --- .../Legacy/CSharpCodeParser.cs | 30 +++++- .../Properties/Resources.Designer.cs | 14 +++ .../Resources.resx | 3 + .../Legacy/CSharpDirectivesTest.cs | 98 +++++++++++++++++++ .../Legacy/CSharpSectionTest.cs | 4 + 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs index 621d7f666e..6d45b45404 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs @@ -101,7 +101,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { foreach (var directive in directives) { - _directiveParsers.Add(directive, handler); + _directiveParsers.Add(directive, () => + { + EnsureDirectiveIsAtStartOfLine(); + handler(); + }); Keywords.Add(directive); // These C# keywords are reserved for use in directives. It's an error to use them outside of @@ -1519,6 +1523,30 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword); } + private void EnsureDirectiveIsAtStartOfLine() + { + // 1 is the offset of the @ transition for the directive. + if (CurrentStart.CharacterIndex > 1) + { + var index = CurrentStart.AbsoluteIndex - 1; + var lineStart = CurrentStart.AbsoluteIndex - CurrentStart.CharacterIndex; + while (--index >= lineStart) + { + var @char = Context.SourceDocument[index]; + + if (!char.IsWhiteSpace(@char)) + { + var currentDirective = CurrentSymbol.Content; + Context.ErrorSink.OnError( + CurrentStart, + Resources.FormatDirectiveMustAppearAtStartOfLine(currentDirective), + length: currentDirective.Length); + break; + } + } + } + } + private void HandleDirective(DirectiveDescriptor descriptor) { Context.Builder.CurrentBlock.Type = BlockKindInternal.Directive; diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs index 1f41dd4ab7..89459d415c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs @@ -430,6 +430,20 @@ namespace Microsoft.AspNetCore.Razor.Language internal static string FormatDiagnostic_CodeTarget_UnsupportedExtension(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("Diagnostic_CodeTarget_UnsupportedExtension"), p0, p1); + /// + /// The '{0}` directive must appear at the start of the line. + /// + internal static string DirectiveMustAppearAtStartOfLine + { + get => GetString("DirectiveMustAppearAtStartOfLine"); + } + + /// + /// The '{0}` directive must appear at the start of the line. + /// + internal static string FormatDirectiveMustAppearAtStartOfLine(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveMustAppearAtStartOfLine"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx index 85d8d679b3..b05f1016c4 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx @@ -207,4 +207,7 @@ The document type '{0}' does not support the extension '{1}'. + + The '{0}` directive must appear at the start of the line. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index d9897cfba5..f51d8e33b1 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -10,6 +10,104 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy { public class CSharpDirectivesTest : CsHtmlCodeParserTestBase { + [Fact] + public void ExtensibleDirectiveDoesNotErorrIfNotAtStartOfLineBecauseOfWhitespace() + { + // Arrange + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddTypeToken()); + + // Act & Assert + ParseCodeBlockTest(Environment.NewLine + " @custom System.Text.Encoding.ASCIIEncoding", + new[] { descriptor }, + new DirectiveBlock( + new DirectiveChunkGenerator(descriptor), + Factory.Code(Environment.NewLine + " ").AsStatement(), + Factory.CodeTransition(), + Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), + Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), + Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false) + .With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0])) + .Accepts(AcceptedCharactersInternal.NonWhiteSpace))); + } + + [Fact] + public void BuiltInDirectiveDoesNotErorrIfNotAtStartOfLineBecauseOfWhitespace() + { + // Act & Assert + ParseCodeBlockTest(Environment.NewLine + " @addTagHelper \"*, Foo\"", + Enumerable.Empty(), + new DirectiveBlock( + Factory.Code(Environment.NewLine + " ").AsStatement(), + Factory.CodeTransition(), + Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ") + .Accepts(AcceptedCharactersInternal.None), + Factory.Code("\"*, Foo\"") + .AsAddTagHelper("\"*, Foo\""))); + } + + [Fact] + public void BuiltInDirectiveErorrsIfNotAtStartOfLine() + { + // Act & Assert + ParseCodeBlockTest("{ @addTagHelper \"*, Foo\"" + Environment.NewLine + "}", + Enumerable.Empty(), + new StatementBlock( + Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None), + Factory.Code(" ") + .AsStatement() + .AutoCompleteWith(autoCompleteString: null, atEndOfSpan: false), + new DirectiveBlock( + Factory.CodeTransition(), + Factory.MetaCode(SyntaxConstants.CSharp.AddTagHelperKeyword + " ") + .Accepts(AcceptedCharactersInternal.None), + Factory.Code("\"*, Foo\"") + .AsAddTagHelper("\"*, Foo\"")), + Factory.Code(Environment.NewLine).AsStatement(), + Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), + new RazorError( + Resources.FormatDirectiveMustAppearAtStartOfLine("addTagHelper"), + new SourceLocation(4, 0, 4), + 12)); + } + + [Fact] + public void ExtensibleDirectiveErorrsIfNotAtStartOfLine() + { + // Arrange + var descriptor = DirectiveDescriptor.CreateDirective( + "custom", + DirectiveKind.SingleLine, + b => b.AddTypeToken()); + + // Act & Assert + ParseCodeBlockTest( + "{ @custom System.Text.Encoding.ASCIIEncoding" + Environment.NewLine + "}", + new[] { descriptor }, + new StatementBlock( + Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None), + Factory.Code(" ") + .AsStatement() + .AutoCompleteWith(autoCompleteString: null, atEndOfSpan: false), + new DirectiveBlock( + new DirectiveChunkGenerator(descriptor), + Factory.CodeTransition(), + Factory.MetaCode("custom").Accepts(AcceptedCharactersInternal.None), + Factory.Span(SpanKindInternal.Code, " ", markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace), + Factory.Span(SpanKindInternal.Code, "System.Text.Encoding.ASCIIEncoding", markup: false) + .With(new DirectiveTokenChunkGenerator(descriptor.Tokens[0])) + .Accepts(AcceptedCharactersInternal.NonWhiteSpace), + Factory.Span(SpanKindInternal.Markup, Environment.NewLine, markup: false).Accepts(AcceptedCharactersInternal.WhiteSpace)), + Factory.EmptyCSharp().AsStatement(), + Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), + new RazorError( + Resources.FormatDirectiveMustAppearAtStartOfLine("custom"), + new SourceLocation(4, 0, 4), + 6)); + } + [Fact] public void DirectiveDescriptor_UnderstandsTypeTokens() { diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpSectionTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpSectionTest.cs index a05f37dd26..3f4511525a 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpSectionTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpSectionTest.cs @@ -164,6 +164,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy Factory.Markup(" ")), Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)), Factory.EmptyHtml()), + new RazorError( + Resources.FormatDirectiveMustAppearAtStartOfLine("section"), + new SourceLocation(16, 0, 16), + 7), new RazorError( LegacyResources.FormatParseError_Sections_Cannot_Be_Nested(LegacyResources.SectionExample_CS), new SourceLocation(15, 0, 15),