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/>
#pragma warning disable 1591
namespace AspNetCore
namespace __BlazorGenerated
{
#line hidden
using TModel = global::System.Object;
@ -9,7 +9,7 @@ namespace AspNetCore
using System.Linq;
using System.Threading.Tasks;
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
private void __RazorDirectiveTokenHelpers__() {

View File

@ -1,12 +1,12 @@
Document -
NamespaceDeclaration - - AspNetCore
NamespaceDeclaration - - __BlazorGenerated
UsingDirective - - TModel = global::System.Object
UsingDirective - (1:0,1 [12] ) - System
UsingDirective - (16:1,1 [32] ) - System.Collections.Generic
UsingDirective - (51:2,1 [17] ) - System.Linq
UsingDirective - (71:3,1 [28] ) - System.Threading.Tasks
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 -
DirectiveToken - (14:0,14 [36] ) - "*, Microsoft.AspNetCore.Components"
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)
|IDisposable|
Generated Location: (618:17,0 [11] )
Generated Location: (662:17,0 [11] )
|IDisposable|
Source Location: (38:1,13 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|this.ToString()|
Generated Location: (1214:33,13 [15] )
Generated Location: (1258:33,13 [15] )
|this.ToString()|
Source Location: (79:3,5 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|string.Format("{0}", "Hello")|
Generated Location: (1359:38,6 [29] )
Generated Location: (1403:38,6 [29] )
|string.Format("{0}", "Hello")|
Source Location: (132:6,12 [37] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml)
|
void IDisposable.Dispose(){ }
|
Generated Location: (1573:45,12 [37] )
Generated Location: (1617:45,12 [37] )
|
void IDisposable.Dispose(){ }
|

View File

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

View File

@ -1,11 +1,11 @@
Document -
NamespaceDeclaration - - AspNetCore
NamespaceDeclaration - - __BlazorGenerated
UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
UsingDirective - (51:2,1 [19] ) - System.Linq
UsingDirective - (71:3,1 [30] ) - System.Threading.Tasks
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
CSharpCode -
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[] 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;
// 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);
}
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(
codeDocument.Source.FilePath,
codeDocument.Source.RelativePath,
out var computedNamespace,
out var computedClass))
codeDocument.Source.FilePath,
codeDocument.Source.RelativePath,
out var computedNamespace,
out var computedClass))
{
// If we can't compute a nice namespace (no relative path) then just generate something
// mangled.
computedNamespace = "AspNetCore";
computedNamespace = BaseNamespace;
var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum());
computedClass = $"AspNetCore_{checksum}";
}
if (MangleClassNames)
{
computedClass = "__" + computedClass;
}
@namespace.Content = computedNamespace;
@class.BaseType = ComponentsApi.ComponentBase.FullTypeName;
@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.Add("public");
@ -82,8 +96,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
@class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, });
}
method.MethodName = CodeGenerationConstants.ComponentBase.BuildRenderTree;
method.ReturnType = "void";
method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree;
method.Modifiers.Clear();
method.Modifiers.Add("protected");
method.Modifiers.Add("override");
@ -91,8 +105,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
method.Parameters.Clear();
method.Parameters.Add(new MethodParameter()
{
TypeName = CodeGenerationConstants.RenderTreeBuilder.FullTypeName,
ParameterName = CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter,
ParameterName = "builder",
TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName,
});
// 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
{
Kind = TokenKind.CSharp,
Content = $"base.{CodeGenerationConstants.ComponentBase.BuildRenderTree}({CodeGenerationConstants.ComponentBase.BuildRenderTreeParameter});"
Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);"
});
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)
{
if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)
@ -158,4 +177,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
internal static bool IsBuildRenderTreeBaseCall(CSharpCodeIntermediateNode node)
=> node.Annotations[BuildRenderTreeBaseCallAnnotation] != null;
}
}
}

View File

@ -207,7 +207,9 @@ namespace Microsoft.CodeAnalysis.Razor
{
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(
type.ContainingAssembly.Name,
@ -218,7 +220,7 @@ namespace Microsoft.CodeAnalysis.Razor
(string)attribute.ConstructorArguments[2].Value,
(string)attribute.ConstructorArguments[3].Value));
}
else if (attribute.AttributeClass == bindInputElement)
else if (attribute.AttributeClass == bindInputElement && attribute.ConstructorArguments.Length == 4)
{
results.Add(new ElementBindData(
type.ContainingAssembly.Name,

View File

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

View File

@ -29,6 +29,17 @@ Copyright (c) .NET Foundation. All rights reserved.
<ProjectCapability Include="DotNetCoreRazorConfiguration" Condition="'$(_TargetingNETCoreApp30OrLater)'=='true'"/>
</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.
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)">
</Target>
<!-- Called by the project system to update generated declaration files -->
<Target
Name="RazorGenerateComponentDeclarationDesignTime"
DependsOnTargets="ResolveRazorConfiguration;ResolveRazorComponentInputs;AssignRazorComponentTargetPaths;RazorGenerateComponentDeclaration">
</Target>
</Project>