diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ClassName.cs similarity index 92% rename from src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ClassName.cs index 509ef56253..6f8033b8e3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/ClassName.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ClassName.cs @@ -4,9 +4,9 @@ using System.Globalization; using System.Text; -namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { - public static class ClassName + internal static class ClassName { public static string GetClassNameFromPath(string path) { @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal category == UnicodeCategory.Format; // Cf } - private static string SanitizeClassName(string inputName) + public static string SanitizeClassName(string inputName) { if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0])) { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs index 983b1a616d..9f61050049 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcRazorTemplateEngine.cs @@ -3,7 +3,6 @@ using System.IO; using System.Text; -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs index b95bcb5fff..da2b2865e6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs index 437e8a40f3..7d61804390 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs @@ -63,13 +63,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { // Beautify the class name since we're using a hierarchy for namespaces. var @class = visitor.FirstClass; + var prefix = Path.GetFileNameWithoutExtension(codeDocument.Source.FileName); if (@class != null && irDocument.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind) { - @class.Name = Path.GetFileNameWithoutExtension(codeDocument.Source.FileName) + "_Page"; + @class.Name = ClassName.SanitizeClassName(prefix + "_Page"); } else if (@class != null && irDocument.DocumentKind == MvcViewDocumentClassifierPass.MvcViewDocumentKind) { - @class.Name = Path.GetFileNameWithoutExtension(codeDocument.Source.FileName) + "_View"; + @class.Name = ClassName.SanitizeClassName(prefix + "_View"); } } @@ -125,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions for (var i = 0; i < segments.Length - 1; i++) { builder.Append('.'); - builder.Append(segments[i]); + builder.Append(ClassName.SanitizeClassName(segments[i])); } @namespace = builder.ToString(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorCodeDocumentExtensions.cs similarity index 88% rename from src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs rename to src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorCodeDocumentExtensions.cs index ae7bc27e38..2e6c220bcc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/Internal/RazorCodeDocumentExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorCodeDocumentExtensions.cs @@ -4,9 +4,9 @@ using System; using Microsoft.AspNetCore.Razor.Language; -namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { - public static class RazorCodeDocumentExtensions + internal static class RazorCodeDocumentExtensions { private const string RelativePathKey = "relative-path"; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs index cbc277788a..321bc172de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorPageDocumentClassifierPass.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs index 8bb53ffc89..753ed80f00 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcRazorTemplateEngineTest.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs index 2e2f8de404..4180601bbd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/MvcViewDocumentClassifierPassTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs index 69c541b8af..32e329d405 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs @@ -196,6 +196,44 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Assert.Equal("AddUser_Page", @class.Name); } + // Handles cases where invalid characters appears in filenames. Note that we don't sanitize the part of + // the namespace that you put in an import, just the file-based-suffix. Garbage in, garbage out. + [Fact] + public void Pass_SetsNamespaceAndClassName_SanitizesClassAndNamespace() + { + // Arrange + var document = new DocumentIRNode(); + var builder = RazorIRBuilder.Create(document); + + builder.Push(new DirectiveIRNode() + { + Descriptor = NamespaceDirective.Directive, + Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0), + }); + builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account" }); + builder.Pop(); + + var @namespace = new NamespaceDeclarationIRNode() { Content = "default" }; + builder.Push(@namespace); + + var @class = new ClassDeclarationIRNode() { Name = "default" }; + builder.Add(@class); + + document.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind; + + var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage-Info/Add+User.cshtml")); + + var pass = new NamespaceDirective.Pass(); + pass.Engine = RazorEngine.CreateEmpty(b => { }); + + // Act + pass.Execute(codeDocument, document); + + // Assert + Assert.Equal("WebApplication.Account.Manage_Info", @namespace.Content); + Assert.Equal("Add_User_Page", @class.Name); + } + // This is the case where the source file sets the namespace. [Fact] public void Pass_SetsNamespaceAndClassName_ComputedFromSource_ForView() diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs index cfcd7b5ab9..f7a9f1e1b1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/RazorPageDocumentClassifierPassTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using Microsoft.AspNetCore.Mvc.Razor.Extensions.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit;