diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.Designer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.Designer.cs index 3a7ce3183a..5d4e39e752 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.Designer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.Designer.cs @@ -223,7 +223,7 @@ namespace Microsoft.AspNetCore.Razor.Language { } /// - /// Looks up a localized string similar to The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file. + /// Looks up a localized string similar to The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor file. /// internal static string PageDirectiveCannotBeImported { get { diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.resx b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.resx index 3fe9868ecd..4a27c934ae 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.resx +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/ComponentResources.resx @@ -163,7 +163,7 @@ TypeName - The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file + The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor file Mark the page as a routable component. diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs index ba2f30fa2b..42a7b4bdaf 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDiagnosticFactory.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components public static readonly RazorDiagnosticDescriptor UnsupportedTagHelperDirective = new RazorDiagnosticDescriptor( $"{DiagnosticPrefix}9978", () => - "The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document." + + "The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. " + "Use '@using ' directive instead.", RazorDiagnosticSeverity.Error); @@ -321,5 +321,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components { return RazorDiagnostic.Create(ChildContentHasInvalidParameterOnComponent, source ?? SourceSpan.Undefined, attribute, element); } + + public static readonly RazorDiagnosticDescriptor UnsupportedComponentImportContent = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}10003", + () => "Markup, code and block directives are not valid in component imports.", + RazorDiagnosticSeverity.Error); + + public static RazorDiagnostic Create_UnsupportedComponentImportContent(SourceSpan? source) + { + return RazorDiagnostic.Create(UnsupportedComponentImportContent, source ?? SourceSpan.Undefined); + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs index 1fa21843f7..37d44227df 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs @@ -70,47 +70,63 @@ namespace Microsoft.AspNetCore.Razor.Language.Components } @namespace.Content = computedNamespace; - - @class.BaseType = ComponentsApi.ComponentBase.FullTypeName; @class.ClassName = computedClass; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); - var documentNode = codeDocument.GetDocumentIntermediateNode(); - var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive); - for (var i = 0; i < typeParamReferences.Count; i++) + if (FileKinds.IsComponentImport(codeDocument.GetFileKind())) { - var typeParamNode = (DirectiveIntermediateNode)typeParamReferences[i].Node; - if (typeParamNode.HasDiagnostics) + // We don't want component imports to be considered as real component. + // But we still want to generate code for it so we can get diagnostics. + @class.BaseType = typeof(object).FullName; + + method.ReturnType = "void"; + method.MethodName = "Execute"; + method.Modifiers.Clear(); + method.Modifiers.Add("protected"); + + method.Parameters.Clear(); + } + else + { + @class.BaseType = ComponentsApi.ComponentBase.FullTypeName; + + var documentNode = codeDocument.GetDocumentIntermediateNode(); + var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive); + for (var i = 0; i < typeParamReferences.Count; i++) { - continue; + var typeParamNode = (DirectiveIntermediateNode)typeParamReferences[i].Node; + if (typeParamNode.HasDiagnostics) + { + continue; + } + + @class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, }); } - @class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, }); + method.ReturnType = "void"; + method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree; + method.Modifiers.Clear(); + method.Modifiers.Add("protected"); + method.Modifiers.Add("override"); + + method.Parameters.Clear(); + method.Parameters.Add(new MethodParameter() + { + ParameterName = "builder", + TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName, + }); + + // We need to call the 'base' method as the first statement. + var callBase = new CSharpCodeIntermediateNode(); + callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); + callBase.Children.Add(new IntermediateToken + { + Kind = TokenKind.CSharp, + Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);" + }); + method.Children.Insert(0, callBase); } - - method.ReturnType = "void"; - method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree; - method.Modifiers.Clear(); - method.Modifiers.Add("protected"); - method.Modifiers.Add("override"); - - method.Parameters.Clear(); - method.Parameters.Add(new MethodParameter() - { - ParameterName = "builder", - TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName, - }); - - // We need to call the 'base' method as the first statement. - var callBase = new CSharpCodeIntermediateNode(); - callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); - callBase.Children.Add(new IntermediateToken - { - Kind = TokenKind.CSharp, - Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);" - }); - method.Children.Insert(0, callBase); } internal static bool IsBuildRenderTreeBaseCall(CSharpCodeIntermediateNode node) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentInjectDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentInjectDirective.cs index 7d123442ad..1af26eb508 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentInjectDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentInjectDirective.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(FileKinds.Component, Directive); + builder.AddDirective(Directive, FileKinds.Component, FileKinds.ComponentImport); builder.Features.Add(new ComponentInjectDirectivePass()); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirective.cs index c746b75d7b..394bed3b56 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirective.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(FileKinds.Component, Directive); + builder.AddDirective(Directive, FileKinds.Component, FileKinds.ComponentImport); builder.Features.Add(new ComponentLayoutDirectivePass()); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirectivePass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirectivePass.cs index b1c36f3152..244c95df78 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirectivePass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLayoutDirectivePass.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components attributeNode.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, - Content = $"[{ComponentsApi.LayoutAttribute.FullTypeName}(typeof({token.Content}))]" + Environment.NewLine, + Content = $"[{ComponentsApi.LayoutAttribute.FullTypeName}(typeof({token.Content}))]", }); // Insert the new attribute on top of the class diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirective.cs index 80ebd9ffca..8fefd172f8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirective.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(FileKinds.Component, Directive); + builder.AddDirective(Directive, FileKinds.Component, FileKinds.ComponentImport); builder.Features.Add(new ComponentPageDirectivePass()); return builder; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirectivePass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirectivePass.cs index 0fe0ac5e26..c751dd57b3 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirectivePass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentPageDirectivePass.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components for (var i = 0; i < directives.Count; i++) { var directive = directives[i]; - if (directive.Node.IsImported()) + if (FileKinds.IsComponentImport(codeDocument.GetFileKind()) || directive.Node.IsImported()) { directive.Node.Diagnostics.Add(ComponentDiagnosticFactory.CreatePageDirective_CannotBeImported(directive.Node.Source.Value)); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTypeParamDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTypeParamDirective.cs index d0e3c12627..d37f84a0df 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTypeParamDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentTypeParamDirective.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(FileKinds.Component, Directive); + builder.AddDirective(Directive, FileKinds.Component, FileKinds.ComponentImport); return builder; } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorDirectiveFeature.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorDirectiveFeature.cs index f766b4b14a..05dd57ebf1 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorDirectiveFeature.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorDirectiveFeature.cs @@ -9,7 +9,21 @@ namespace Microsoft.AspNetCore.Razor.Language { internal class DefaultRazorDirectiveFeature : RazorEngineFeatureBase, IRazorDirectiveFeature, IConfigureRazorParserOptionsFeature { - public ICollection Directives { get; } = new List(); + // To maintain backwards compatibility, adding to this list will default to legacy file kind. + public ICollection Directives + { + get + { + ICollection result; + if (!DirectivesByFileKind.TryGetValue(FileKinds.Legacy, out result)) + { + result = new List(); + DirectivesByFileKind.Add(FileKinds.Legacy, result); + } + + return result; + } + } public IDictionary> DirectivesByFileKind { get; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -24,22 +38,11 @@ namespace Microsoft.AspNetCore.Razor.Language options.Directives.Clear(); - foreach (var directive in Directives) - { - options.Directives.Add(directive); - } - - if (options.FileKind != null && DirectivesByFileKind.TryGetValue(options.FileKind, out var directives)) + var fileKind = options.FileKind ?? FileKinds.Legacy; + if (DirectivesByFileKind.TryGetValue(fileKind, out var directives)) { foreach (var directive in directives) { - // Replace any non-file-kind-specific directives - var replaces = options.Directives.Where(d => string.Equals(d.Directive, directive.Directive, StringComparison.Ordinal)).ToArray(); - foreach (var replace in replaces) - { - options.Directives.Remove(replace); - } - options.Directives.Add(directive); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs index 38f05d593b..232ce738fb 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -45,7 +45,17 @@ namespace Microsoft.AspNetCore.Razor.Language // We need to decide up front if this document is a "component" file. This will affect how // lowering behaves. LoweringVisitor visitor; - if (FileKinds.IsComponent(codeDocument.GetFileKind()) && + if (FileKinds.IsComponentImport(codeDocument.GetFileKind()) && + syntaxTree.Options.FeatureFlags.AllowComponentFileKind) + { + visitor = new ComponentImportFileKindVisitor(document, builder, syntaxTree.Options.FeatureFlags) + { + SourceDocument = syntaxTree.Source, + }; + + visitor.Visit(syntaxTree.Root); + } + else if (FileKinds.IsComponent(codeDocument.GetFileKind()) && syntaxTree.Options.FeatureFlags.AllowComponentFileKind) { visitor = new ComponentFileKindVisitor(document, builder, syntaxTree.Options.FeatureFlags) @@ -1856,6 +1866,58 @@ namespace Microsoft.AspNetCore.Razor.Language } } + private class ComponentImportFileKindVisitor : LoweringVisitor + { + public ComponentImportFileKindVisitor( + DocumentIntermediateNode document, + IntermediateNodeBuilder builder, + RazorParserFeatureFlags featureFlags) + : base(document, builder, featureFlags) + { + } + + public override void DefaultVisit(SyntaxNode node) + { + base.DefaultVisit(node); + } + + public override void VisitMarkupElement(MarkupElementSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + + public override void VisitMarkupCommentBlock(MarkupCommentBlockSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + + public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + + public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + + public override void VisitCSharpExplicitExpression(CSharpExplicitExpressionSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + + public override void VisitCSharpStatement(CSharpStatementSyntax node) + { + _document.Diagnostics.Add( + ComponentDiagnosticFactory.Create_UnsupportedComponentImportContent(BuildSourceSpanFromNode(node))); + } + } + private class ImportsVisitor : LoweringVisitor { public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, RazorParserFeatureFlags featureFlags) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs index 34a2d77613..e4a30a841a 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs @@ -37,7 +37,9 @@ namespace Microsoft.AspNetCore.Razor.Language // The imports come logically before the main razor file and are in the order they // should be processed. DirectiveVisitor visitor = null; - if (FileKinds.IsComponent(codeDocument.GetFileKind())) + var parserOptions = codeDocument.GetParserOptions(); + if (FileKinds.IsComponent(codeDocument.GetFileKind()) && + (parserOptions == null || parserOptions.FeatureFlags.AllowComponentFileKind)) { codeDocument.TryComputeNamespaceAndClass(out var currentNamespace, out var _); visitor = new ComponentDirectiveVisitor(codeDocument.Source.FilePath, descriptors, currentNamespace); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/FunctionsDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/FunctionsDirective.cs index 630272cdef..8947e20134 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/FunctionsDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/FunctionsDirective.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(Directive); + builder.AddDirective(Directive, FileKinds.Legacy, FileKinds.Component); builder.Features.Add(new FunctionsDirectivePass()); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/ImplementsDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/ImplementsDirective.cs index b160ff8773..4ee1657a27 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/ImplementsDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/ImplementsDirective.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(Directive); + builder.AddDirective(Directive, FileKinds.Legacy, FileKinds.Component, FileKinds.ComponentImport); builder.Features.Add(new ImplementsDirectivePass()); } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/InheritsDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/InheritsDirective.cs index 12cdb3708a..74fa396bcc 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/InheritsDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/InheritsDirective.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(Directive); + builder.AddDirective(Directive, FileKinds.Legacy, FileKinds.Component, FileKinds.ComponentImport); builder.Features.Add(new InheritsDirectivePass()); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/SectionDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/SectionDirective.cs index b1f8633122..9040e18688 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/SectionDirective.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/SectionDirective.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(builder)); } - builder.AddDirective(Directive); + builder.AddDirective(Directive, FileKinds.Legacy, FileKinds.Component); builder.Features.Add(new SectionDirectivePass()); builder.AddTargetExtension(new SectionTargetExtension()); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/FileKinds.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/FileKinds.cs index c69df04e60..605c601cc7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/FileKinds.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/FileKinds.cs @@ -27,6 +27,23 @@ namespace Microsoft.AspNetCore.Razor.Language return string.Equals(fileKind, FileKinds.ComponentImport, StringComparison.OrdinalIgnoreCase); } + public static string GetComponentFileKindFromFilePath(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + + if (string.Equals(ComponentMetadata.ImportsFileName, Path.GetFileName(filePath), StringComparison.Ordinal)) + { + return FileKinds.ComponentImport; + } + else + { + return FileKinds.Component; + } + } + public static string GetFileKindFromFilePath(string filePath) { if (filePath == null) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs index 09fdf087f4..89e809fb09 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs @@ -166,35 +166,38 @@ namespace Microsoft.AspNetCore.Razor.Language /// Adds the specified for the provided file kind. /// /// The . - /// The file kind, for which to register the directive. See . /// The to add. + /// The file kinds, for which to register the directive. See . /// The . - public static RazorProjectEngineBuilder AddDirective(this RazorProjectEngineBuilder builder, string fileKind, DirectiveDescriptor directive) + public static RazorProjectEngineBuilder AddDirective(this RazorProjectEngineBuilder builder, DirectiveDescriptor directive, params string[] fileKinds) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (fileKind == null) - { - throw new ArgumentNullException(nameof(fileKind)); - } - if (directive == null) { throw new ArgumentNullException(nameof(directive)); } - var directiveFeature = GetDirectiveFeature(builder); - - if (!directiveFeature.DirectivesByFileKind.TryGetValue(fileKind, out var directives)) + if (fileKinds == null) { - directives = new List(); - directiveFeature.DirectivesByFileKind.Add(fileKind, directives); + throw new ArgumentNullException(nameof(fileKinds)); } - directives.Add(directive); + var directiveFeature = GetDirectiveFeature(builder); + + foreach (var fileKind in fileKinds) + { + if (!directiveFeature.DirectivesByFileKind.TryGetValue(fileKind, out var directives)) + { + directives = new List(); + directiveFeature.DirectivesByFileKind.Add(fileKind, directives); + } + + directives.Add(directive); + } return builder; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index 3501096bde..5715739c67 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Components; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; using Microsoft.Extensions.CommandLineUtils; @@ -286,6 +287,10 @@ namespace Microsoft.AspNetCore.Razor.Tools { var outputPath = Path.Combine(projectDirectory, outputs[i]); var fileKind = fileKinds.Count > 0 ? fileKinds[i] : "mvc"; + if (Language.FileKinds.IsComponent(fileKind)) + { + fileKind = Language.FileKinds.GetComponentFileKindFromFilePath(sources[i]); + } items[i] = new SourceItem(sources[i], outputs[i], relativePath[i], fileKind); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs index 75440d18f4..5266adac10 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs @@ -59,6 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { AdditionalSyntaxTrees = new List(); AdditionalRazorItems = new List(); + ImportItems = new List(); BaseCompilation = DefaultBaseCompilation; Configuration = RazorConfiguration.Default; @@ -72,6 +73,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests internal List AdditionalRazorItems { get; } + internal List ImportItems { get; } + internal List AdditionalSyntaxTrees { get; } internal virtual CSharpCompilation BaseCompilation { get; } @@ -118,6 +121,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests // Turn off checksums, we're testing code generation. b.Features.Add(new SuppressChecksum()); + b.Features.Add(new TestImportProjectFeature(ImportItems)); + if (LineEnding != null) { b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding)); @@ -135,7 +140,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests }); } - internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent) + internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent, string fileKind = null) { var fullPath = WorkingDirectory + PathSeparator + cshtmlRelativePath; @@ -156,7 +161,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests physicalPath: fullPath, relativePhysicalPath: cshtmlRelativePath, basePath: WorkingDirectory, - fileKind: FileKind) + fileKind: fileKind ?? FileKind) { Content = cshtmlContent.TrimStart(), }; @@ -167,7 +172,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests return CompileToCSharp(DefaultFileName, cshtmlContent, throwOnFailure); } - protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent, bool throwOnFailure = true) + protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent, bool throwOnFailure = true, string fileKind = null) { if (DeclarationOnly && DesignTime) { @@ -197,7 +202,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests } // Result of generating declarations - var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent); + var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent, fileKind); codeDocument = projectEngine.ProcessDeclarationOnly(projectItem); var declaration = new CompileToCSharpResult { @@ -243,7 +248,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests // This will include the built-in components. var projectEngine = CreateProjectEngine(Configuration, BaseCompilation.References.ToArray()); - var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent); + var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent, fileKind); RazorCodeDocument codeDocument; if (DeclarationOnly) @@ -456,5 +461,22 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests codeDocument.Items[key] = LineEnding; } } + + private class TestImportProjectFeature : IImportProjectFeature + { + private List _imports; + + public TestImportProjectFeature(List imports) + { + _imports = imports; + } + + public RazorProjectEngine ProjectEngine { get; set; } + + public IReadOnlyList GetImports(RazorProjectItem projectItem) + { + return _imports; + } + } } }