Enabling features for Components in VS

This is the final set of enabling features for VS.. including:
- Adding component types to IDE project engine
- Using file kind in the editor
- Enabling component documents in the project system
- Fixing some bugs in the xaml/msbuild authoring
- Adding a missing capability for component projects

The only thing here that probably bears explaining is the class name
mangling. This is a carry over from Blazor, basically because the
generated code is part of the workspace, we have to mangle the class
name to avoid collisions. The work to resolve this is tracked
separately, and will require coordination from a few teams to resolve.
\n\nCommit migrated from d0a8aa3f97
This commit is contained in:
Ryan Nowak 2019-01-02 18:21:34 -08:00
parent 9c10b29f87
commit d46aa2e2bd
9 changed files with 82 additions and 42 deletions

View File

@ -1,6 +1,6 @@
// <auto-generated/> // <auto-generated/>
#pragma warning disable 1591 #pragma warning disable 1591
namespace AspNetCore namespace __BlazorGenerated
{ {
#line hidden #line hidden
using TModel = global::System.Object; using TModel = global::System.Object;
@ -9,7 +9,7 @@ namespace AspNetCore
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
public class BasicComponent : Microsoft.AspNetCore.Components.ComponentBase, IDisposable public class AspNetCore_d3c3d6059615673cb46fc4974164d61eabadb890 : Microsoft.AspNetCore.Components.ComponentBase, IDisposable
{ {
#pragma warning disable 219 #pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() { private void __RazorDirectiveTokenHelpers__() {

View File

@ -1,12 +1,12 @@
Document - Document -
NamespaceDeclaration - - AspNetCore NamespaceDeclaration - - __BlazorGenerated
UsingDirective - - TModel = global::System.Object UsingDirective - - TModel = global::System.Object
UsingDirective - (1:0,1 [12] ) - System UsingDirective - (1:0,1 [12] ) - System
UsingDirective - (16:1,1 [32] ) - System.Collections.Generic UsingDirective - (16:1,1 [32] ) - System.Collections.Generic
UsingDirective - (51:2,1 [17] ) - System.Linq UsingDirective - (51:2,1 [17] ) - System.Linq
UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks
UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - BasicComponent - Microsoft.AspNetCore.Components.ComponentBase - IDisposable ClassDeclaration - - public - AspNetCore_d3c3d6059615673cb46fc4974164d61eabadb890 - Microsoft.AspNetCore.Components.ComponentBase - IDisposable
DesignTimeDirective - DesignTimeDirective -
DirectiveToken - (14:0,14 [36] ) - "*, Microsoft.AspNetCore.Components" DirectiveToken - (14:0,14 [36] ) - "*, Microsoft.AspNetCore.Components"
DirectiveToken - (12:0,12 [11] BasicComponent.cshtml) - IDisposable DirectiveToken - (12:0,12 [11] BasicComponent.cshtml) - IDisposable

View File

@ -1,23 +1,23 @@
Source Location: (12:0,12 [11] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) Source Location: (12:0,12 [11] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|IDisposable| |IDisposable|
Generated Location: (618:17,0 [11] ) Generated Location: (662:17,0 [11] )
|IDisposable| |IDisposable|
Source Location: (38:1,13 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) Source Location: (38:1,13 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|this.ToString()| |this.ToString()|
Generated Location: (1214:33,13 [15] ) Generated Location: (1258:33,13 [15] )
|this.ToString()| |this.ToString()|
Source Location: (79:3,5 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) Source Location: (79:3,5 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|string.Format("{0}", "Hello")| |string.Format("{0}", "Hello")|
Generated Location: (1359:38,6 [29] ) Generated Location: (1403:38,6 [29] )
|string.Format("{0}", "Hello")| |string.Format("{0}", "Hello")|
Source Location: (132:6,12 [37] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) Source Location: (132:6,12 [37] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
| |
void IDisposable.Dispose(){ } void IDisposable.Dispose(){ }
| |
Generated Location: (1573:45,12 [37] ) Generated Location: (1617:45,12 [37] )
| |
void IDisposable.Dispose(){ } void IDisposable.Dispose(){ }
| |

View File

@ -1,7 +1,7 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d3c3d6059615673cb46fc4974164d61eabadb890" #pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d3c3d6059615673cb46fc4974164d61eabadb890"
// <auto-generated/> // <auto-generated/>
#pragma warning disable 1591 #pragma warning disable 1591
namespace AspNetCore namespace __BlazorGenerated
{ {
#line hidden #line hidden
using System; using System;
@ -9,7 +9,7 @@ namespace AspNetCore
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
public class BasicComponent : Microsoft.AspNetCore.Components.ComponentBase, IDisposable public class AspNetCore_d3c3d6059615673cb46fc4974164d61eabadb890 : Microsoft.AspNetCore.Components.ComponentBase, IDisposable
{ {
#pragma warning disable 1998 #pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)

View File

@ -1,11 +1,11 @@
Document - Document -
NamespaceDeclaration - - AspNetCore NamespaceDeclaration - - __BlazorGenerated
UsingDirective - (1:0,1 [14] ) - System UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
UsingDirective - (51:2,1 [19] ) - System.Linq UsingDirective - (51:2,1 [19] ) - System.Linq
UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks
UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - BasicComponent - Microsoft.AspNetCore.Components.ComponentBase - IDisposable ClassDeclaration - - public - AspNetCore_d3c3d6059615673cb46fc4974164d61eabadb890 - Microsoft.AspNetCore.Components.ComponentBase - IDisposable
MethodDeclaration - - protected override - void - BuildRenderTree MethodDeclaration - - protected override - void - BuildRenderTree
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - base.BuildRenderTree(builder); IntermediateToken - - CSharp - base.BuildRenderTree(builder);

View File

@ -17,6 +17,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
private static readonly char[] PathSeparators = new char[] { '/', '\\' }; private static readonly char[] PathSeparators = new char[] { '/', '\\' };
private static readonly char[] NamespaceSeparators = new char[] { '.' }; private static readonly char[] NamespaceSeparators = new char[] { '.' };
/// <summary>
/// The base namespace.
/// </summary>
// This is a fallback value and will only be used if we can't compute
// a reasonable namespace.
public string BaseNamespace { get; set; } = "__BlazorGenerated";
/// <summary>
/// Gets or sets whether to mangle class names.
///
/// Set to true in the IDE so we can generated mangled class names. This is needed
/// to avoid conflicts between generated design-time code and the code in the editor.
///
/// A better workaround for this would be to create a singlefilegenerator that overrides
/// the codegen process when a document is open, but this is more involved, so hacking
/// it for now.
/// </summary>
public bool MangleClassNames { get; set; } = false;
protected override string DocumentKind => ComponentDocumentKind; protected override string DocumentKind => ComponentDocumentKind;
// Ensure this runs before the MVC classifiers which have Order = 0 // Ensure this runs before the MVC classifiers which have Order = 0
@ -32,40 +51,35 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
return new ComponentCodeTarget(options, TargetExtensions); return new ComponentCodeTarget(options, TargetExtensions);
} }
protected override void OnDocumentStructureCreated(RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) /// <inheritdoc />
protected override void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIntermediateNode @namespace,
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{ {
base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
if (!TryComputeNamespaceAndClass( if (!TryComputeNamespaceAndClass(
codeDocument.Source.FilePath, codeDocument.Source.FilePath,
codeDocument.Source.RelativePath, codeDocument.Source.RelativePath,
out var computedNamespace, out var computedNamespace,
out var computedClass)) out var computedClass))
{ {
// If we can't compute a nice namespace (no relative path) then just generate something // If we can't compute a nice namespace (no relative path) then just generate something
// mangled. // mangled.
computedNamespace = "AspNetCore"; computedNamespace = BaseNamespace;
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
computedClass = $"AspNetCore_{checksum}"; computedClass = $"AspNetCore_{checksum}";
} }
if (MangleClassNames)
{
computedClass = "__" + computedClass;
}
@namespace.Content = computedNamespace; @namespace.Content = computedNamespace;
@class.BaseType = ComponentsApi.ComponentBase.FullTypeName;
@class.ClassName = computedClass; @class.ClassName = computedClass;
@class.BaseType = $"{CodeGenerationConstants.ComponentBase.FullTypeName}";
var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath;
if (string.IsNullOrEmpty(filePath))
{
// It's possible for a Razor document to not have a file path.
// Eg. When we try to generate code for an in memory document like default imports.
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
@class.ClassName = $"AspNetCore_{checksum}";
}
else
{
@class.ClassName = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath));
}
@class.Modifiers.Clear(); @class.Modifiers.Clear();
@class.Modifiers.Add("public"); @class.Modifiers.Add("public");
@ -82,8 +96,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
@class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, }); @class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, });
} }
method.MethodName = CodeGenerationConstants.ComponentBase.BuildRenderTree;
method.ReturnType = "void"; method.ReturnType = "void";
method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree;
method.Modifiers.Clear(); method.Modifiers.Clear();
method.Modifiers.Add("protected"); method.Modifiers.Add("protected");
method.Modifiers.Add("override"); method.Modifiers.Add("override");
@ -91,8 +105,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
method.Parameters.Clear(); method.Parameters.Clear();
method.Parameters.Add(new MethodParameter() method.Parameters.Add(new MethodParameter()
{ {
TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName, ParameterName = "builder",
ParameterName = CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter, TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName,
}); });
// We need to call the 'base' method as the first statement. // We need to call the 'base' method as the first statement.
@ -101,11 +115,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
callBase.Children.Add(new IntermediateToken callBase.Children.Add(new IntermediateToken
{ {
Kind = TokenKind.CSharp, Kind = TokenKind.CSharp,
Content = $"base.{CodeGenerationConstants.ComponentBase.BuildRenderTree}({CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter});" Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);"
}); });
method.Children.Insert(0, callBase); method.Children.Insert(0, callBase);
} }
// In general documents will have a relative path (relative to the project root).
// We can only really compute a nice class/namespace when we know a relative path.
//
// However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't
// set up correctly.
private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class) private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class)
{ {
if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)

View File

@ -207,7 +207,9 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
var attribute = attributes[j]; var attribute = attributes[j];
if (attribute.AttributeClass == bindElement) // We need to check the constructor argument length here, because this can show up as 0
// if the language service fails to initialize. This is an invalid case, so skip it.
if (attribute.AttributeClass == bindElement && attribute.ConstructorArguments.Length == 4)
{ {
results.Add(new ElementBindData( results.Add(new ElementBindData(
type.ContainingAssembly.Name, type.ContainingAssembly.Name,
@ -218,7 +220,7 @@ namespace Microsoft.CodeAnalysis.Razor
(string)attribute.ConstructorArguments[2].Value, (string)attribute.ConstructorArguments[2].Value,
(string)attribute.ConstructorArguments[3].Value)); (string)attribute.ConstructorArguments[3].Value));
} }
else if (attribute.AttributeClass == bindInputElement) else if (attribute.AttributeClass == bindInputElement && attribute.ConstructorArguments.Length == 4)
{ {
results.Add(new ElementBindData( results.Add(new ElementBindData(
type.ContainingAssembly.Name, type.ContainingAssembly.Name,

View File

@ -49,7 +49,9 @@ Copyright (c) .NET Foundation. All rights reserved.
Condition="'%(RazorComponentWithTargetPath.GeneratedDeclaration)' == ''" /> Condition="'%(RazorComponentWithTargetPath.GeneratedDeclaration)' == ''" />
<ItemGroup> <ItemGroup>
<_RazorComponentDeclaration Include="%(RazorComponentWithTargetPath.GeneratedDeclaration)" /> <_RazorComponentDeclaration Include="@(RazorComponentWithTargetPath->'%(GeneratedDeclaration)')">
<DependentUpon>%(Identity)</DependentUpon>
</_RazorComponentDeclaration>
<_RazorComponentDefinition Include="%(RazorComponentWithTargetPath.GeneratedOutput)" /> <_RazorComponentDefinition Include="%(RazorComponentWithTargetPath.GeneratedOutput)" />
</ItemGroup> </ItemGroup>
</Target> </Target>

View File

@ -29,6 +29,17 @@ Copyright (c) .NET Foundation. All rights reserved.
<ProjectCapability Include="DotNetCoreRazorConfiguration" Condition="'$(_TargetingNETCoreApp30OrLater)'=='true'"/> <ProjectCapability Include="DotNetCoreRazorConfiguration" Condition="'$(_TargetingNETCoreApp30OrLater)'=='true'"/>
</ItemGroup> </ItemGroup>
<!--
For now we need to treat component files as if they have a single file generator. This will allow us
to trigger a workspace update for the declaration files when they change.
-->
<ItemGroup>
<Content Update="@(RazorComponent)">
<Generator>MSBuild:RazorGenerateComponentDeclarationDesignTime</Generator>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!-- <!--
WebSdk imports these capabilities for nesting in DotNetCoreWeb projects. WebSdk imports these capabilities for nesting in DotNetCoreWeb projects.
Conditinally import these capabilities if the project isn't targeting the WebSdk. Conditinally import these capabilities if the project isn't targeting the WebSdk.
@ -69,4 +80,10 @@ Copyright (c) .NET Foundation. All rights reserved.
Returns="@(RazorComponentWithTargetPath)"> Returns="@(RazorComponentWithTargetPath)">
</Target> </Target>
<!-- Called by the project system to update generated declaration files -->
<Target
Name="RazorGenerateComponentDeclarationDesignTime"
DependsOnTargets="ResolveRazorConfiguration;ResolveRazorComponentInputs;AssignRazorComponentTargetPaths;RazorGenerateComponentDeclaration">
</Target>
</Project> </Project>