[FUSE] Provide intellisense for @inject directives (#10771)

* When @Inject is missing the member name, generate a syntactically valid c# identifier so we get intellisense
* Emit an empty section when there is no typename
* Add tests and update baselines
This commit is contained in:
Chris Sienkiewicz 2024-08-26 10:14:26 -07:00 коммит произвёл GitHub
Родитель 3423142c7d
Коммит aa024eb33f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
27 изменённых файлов: 358 добавлений и 136 удалений

Просмотреть файл

@ -69,6 +69,30 @@ public class MyService<TModel>
Assert.NotEmpty(compiled.CodeDocument.GetCSharpDocument().Diagnostics);
}
[Fact]
public void IncompleteDirectives_Runtime()
{
// Arrange
AddCSharpSyntaxTree(@"
public class MyService<TModel>
{
public string Html { get; set; }
}");
var projectItem = CreateProjectItemFromFile();
// Act
var compiled = CompileToCSharp(projectItem, designTime: false);
// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(compiled.CodeDocument.GetCSharpDocument());
AssertSourceMappingsMatchBaseline(compiled.CodeDocument);
// We expect this test to generate a bunch of errors.
Assert.True(compiled.CodeDocument.GetCSharpDocument().Diagnostics.Length > 0);
}
[Fact]
public void InheritsViewModel_DesignTime()
{

Просмотреть файл

@ -60,3 +60,6 @@
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -

Просмотреть файл

@ -1,9 +1,10 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "844eb91b909a14b78feddd5e6866563b5a75e021"
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "c83d1c26cf039a87fc6aedc860fd9d28a34d96dfb2e405e6af3918602ca27755"
// <auto-generated/>
#pragma warning disable 1591
[assembly: global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute(typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives), @"mvc.1.0.view", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml")]
namespace AspNetCore
{
#line hidden
#line default
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,31 +12,38 @@ namespace AspNetCore
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
#line default
#line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"Sha256", @"c83d1c26cf039a87fc6aedc860fd9d28a34d96dfb2e405e6af3918602ca27755", @"/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml")]
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(83, 4, true);
WriteLiteral("\r\n");
WriteLiteral("\r\n");
WriteLiteral("\r\n\r\n");
EndContext();
BeginContext(93, 2, true);
WriteLiteral("\r\n");
EndContext();
BeginContext(102, 4, true);
WriteLiteral("\r\n\r\n");
EndContext();
BeginContext(113, 2, true);
WriteLiteral("\r\n");
EndContext();
BeginContext(123, 2, true);
WriteLiteral("\r\n");
EndContext();
BeginContext(150, 2, true);
WriteLiteral("\r\n");
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public
#line (8,9)-(8,18) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
MyService<dynamic>
#line default
#line hidden
Member_test { get; private set; }
#line (7,9)-(7,9) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
#line (6,8)-(6,8) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]

Просмотреть файл

@ -1,6 +1,6 @@
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,7): Error RZ9999: The 'model' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ9999: The 'model' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,8): Error RZ9999: The 'model' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(6,8): Error RZ9999: The 'inject' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,9): Error RZ9999: The 'inject' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,26): Error RZ9999: The 'inject' directive expects an identifier.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(3,7): Error RZ1013: The 'model' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,1): Error RZ2001: The 'model' directive may only occur once per document.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(4,8): Error RZ1013: The 'model' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(6,8): Error RZ1013: The 'inject' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(7,9): Error RZ1013: The 'inject' directive expects a type name.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(8,26): Error RZ1015: The 'inject' directive expects an identifier.

Просмотреть файл

@ -1,4 +1,5 @@
Document -
RazorCompiledItemAttribute -
NamespaceDeclaration - - AspNetCore
UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
@ -7,50 +8,36 @@
UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
RazorSourceChecksumAttribute -
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync
CSharpCode -
IntermediateToken - - CSharp - BeginContext(83, 4, true);
HtmlContent - (83:0,83 [4] IncompleteDirectives.cshtml)
IntermediateToken - (83:0,83 [4] IncompleteDirectives.cshtml) - Html - \n\n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
HtmlContent - (85:1,0 [2] IncompleteDirectives.cshtml)
LazyIntermediateToken - (85:1,0 [2] IncompleteDirectives.cshtml) - Html - \n
MalformedDirective - (87:2,0 [6] IncompleteDirectives.cshtml) - model
CSharpCode -
IntermediateToken - - CSharp - BeginContext(93, 2, true);
CSharpCode - (93:2,6 [0] IncompleteDirectives.cshtml)
LazyIntermediateToken - (93:2,6 [0] IncompleteDirectives.cshtml) - CSharp -
HtmlContent - (93:2,6 [2] IncompleteDirectives.cshtml)
IntermediateToken - (93:2,6 [2] IncompleteDirectives.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
LazyIntermediateToken - (93:2,6 [2] IncompleteDirectives.cshtml) - Html - \n
MalformedDirective - (95:3,0 [7] IncompleteDirectives.cshtml) - model
CSharpCode -
IntermediateToken - - CSharp - BeginContext(102, 4, true);
DirectiveToken - (102:3,7 [0] IncompleteDirectives.cshtml) -
HtmlContent - (102:3,7 [4] IncompleteDirectives.cshtml)
IntermediateToken - (102:3,7 [4] IncompleteDirectives.cshtml) - Html - \n\n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
LazyIntermediateToken - (102:3,7 [4] IncompleteDirectives.cshtml) - Html - \n\n
MalformedDirective - (106:5,0 [7] IncompleteDirectives.cshtml) - inject
CSharpCode -
IntermediateToken - - CSharp - BeginContext(113, 2, true);
CSharpCode - (113:5,7 [0] IncompleteDirectives.cshtml)
LazyIntermediateToken - (113:5,7 [0] IncompleteDirectives.cshtml) - CSharp -
HtmlContent - (113:5,7 [2] IncompleteDirectives.cshtml)
IntermediateToken - (113:5,7 [2] IncompleteDirectives.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
LazyIntermediateToken - (113:5,7 [2] IncompleteDirectives.cshtml) - Html - \n
MalformedDirective - (115:6,0 [8] IncompleteDirectives.cshtml) - inject
CSharpCode -
IntermediateToken - - CSharp - BeginContext(123, 2, true);
DirectiveToken - (123:6,8 [0] IncompleteDirectives.cshtml) -
HtmlContent - (123:6,8 [2] IncompleteDirectives.cshtml)
IntermediateToken - (123:6,8 [2] IncompleteDirectives.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
LazyIntermediateToken - (123:6,8 [2] IncompleteDirectives.cshtml) - Html - \n
MalformedDirective - (125:7,0 [25] IncompleteDirectives.cshtml) - inject
DirectiveToken - (133:7,8 [17] IncompleteDirectives.cshtml) - MyService<TModel>
CSharpCode -
IntermediateToken - - CSharp - BeginContext(150, 2, true);
HtmlContent - (150:7,25 [2] IncompleteDirectives.cshtml)
IntermediateToken - (150:7,25 [2] IncompleteDirectives.cshtml) - Html - \n
CSharpCode -
IntermediateToken - - CSharp - EndContext();
LazyIntermediateToken - (150:7,25 [2] IncompleteDirectives.cshtml) - Html - \n
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -

Просмотреть файл

@ -0,0 +1,15 @@
Source Location: (133:7,8 [9] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml)
|MyService|
Generated Location: (1895:33,0 [9] )
|MyService|
Source Location: (123:6,8 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml)
||
Generated Location: (2096:39,0 [0] )
||
Source Location: (113:5,7 [0] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml)
||
Generated Location: (2233:43,0 [0] )
||

Просмотреть файл

@ -76,6 +76,9 @@
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -
CSharpCode -
IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>)PageContext?.ViewData;
CSharpCode -

Просмотреть файл

@ -50,6 +50,22 @@ namespace AspNetCore
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public
#line (12,9)-(12,18) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
MyService<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>
#line default
#line hidden
Member_test { get; private set; }
#line (11,9)-(11,9) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
#line (10,8)-(10,8) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]

Просмотреть файл

@ -91,6 +91,9 @@
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -
CSharpCode -
IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>)PageContext?.ViewData;
CSharpCode -

Просмотреть файл

@ -78,6 +78,9 @@
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -
CSharpCode -
IntermediateToken - - CSharp - #nullable restore\npublic global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>)PageContext?.ViewData!;\n#nullable disable
CSharpCode -

Просмотреть файл

@ -35,6 +35,29 @@ namespace AspNetCore
WriteLiteral("\r\n");
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public
#nullable restore
#line (12,9)-(12,18) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
MyService<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>
#line default
#line hidden
#nullable disable
Member_test { get; private set; }
= default!;
#nullable restore
#line (11,9)-(11,9) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
#nullable disable
#nullable restore
#line (10,8)-(10,8) "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml"
#line default
#line hidden
#nullable disable
#nullable restore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } = default!;

Просмотреть файл

@ -55,6 +55,9 @@
Inject -
Inject -
Inject -
Inject -
Inject -
Inject -
CSharpCode -
IntermediateToken - - CSharp - #nullable restore\npublic global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives>)PageContext?.ViewData!;\n#nullable disable
CSharpCode -

Просмотреть файл

@ -100,6 +100,23 @@ public class ComponentDirectiveIntegrationTest : RazorIntegrationTestBase
s => AssertEx.Equal("private TestNamespace.IMyService2 Test.TestComponent.MyService2 { get; set; }", s.ToTestDisplayString()));
}
[Fact]
public void SupportsIncompleteInjectDirectives()
{
var component = CompileToComponent("""
@inject
@inject DateTime
@inject DateTime Value
""");
// Assert 1: Compiled type has correct properties
var injectableProperties = component.GetMembers().OfType<IPropertySymbol>()
.Where(p => p.GetAttributes().Any(a => a.AttributeClass.Name == "InjectAttribute"));
Assert.Collection(injectableProperties.OrderBy(p => p.Name),
s => AssertEx.Equal("private System.DateTime Test.TestComponent.Member___UniqueIdSuppressedForTesting__ { get; set; }", s.ToTestDisplayString()),
s => AssertEx.Equal("private System.DateTime Test.TestComponent.Value { get; set; }", s.ToTestDisplayString()));
}
private const string AdditionalCode =
"""
using Microsoft.AspNetCore.Components;

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -25,30 +26,31 @@ internal class ComponentInjectDirectivePass : IntermediateNodePassBase, IRazorDi
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
var tokens = directive.Children.OfType<DirectiveTokenIntermediateNode>().ToArray();
var isMalformed = directive is MalformedDirectiveIntermediateNode;
var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content);
Debug.Assert(hasType || isMalformed);
var typeName = hasType ? tokens[0].Content : string.Empty;
var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan();
var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content);
Debug.Assert(hasMemberName || isMalformed);
var memberName = hasMemberName ? tokens[1].Content : null;
var memberSpan = hasMemberName ? tokens[1].Source : null;
if (hasMemberName && !properties.Add(memberName))
{
continue;
}
var typeName = tokens[0].Content;
var typeSpan = tokens[0].Source;
var memberName = tokens[1].Content;
var memberSpan = tokens[1].Source;
if (!properties.Add(memberName))
{
continue;
}
classNode.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan));
classNode.Children.Add(new ComponentInjectIntermediateNode(typeName, memberName, typeSpan, memberSpan, isMalformed));
}
}
private class Visitor : IntermediateNodeWalker
{
public IList<DirectiveIntermediateNode> Directives { get; }
= new List<DirectiveIntermediateNode>();
public IList<IntermediateNode> Directives { get; } = [];
public override void VisitDirective(DirectiveIntermediateNode node)
{
@ -57,5 +59,13 @@ internal class ComponentInjectDirectivePass : IntermediateNodePassBase, IRazorDi
Directives.Add(node);
}
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (node.Directive == ComponentInjectDirective.Directive)
{
Directives.Add(node);
}
}
}
}

Просмотреть файл

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Components;
@ -18,13 +19,14 @@ internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode
"private" // Encapsulation is the default
};
public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan)
public ComponentInjectIntermediateNode(string typeName, string memberName, SourceSpan? typeSpan, SourceSpan? memberSpan, bool isMalformed)
{
TypeName = typeName;
MemberName = memberName;
TypeSpan = typeSpan;
MemberSpan = memberSpan;
}
IsMalformed = isMalformed;
}
public string TypeName { get; }
@ -34,8 +36,9 @@ internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode
public SourceSpan? MemberSpan { get; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public bool IsMalformed { get; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
{
@ -59,13 +62,26 @@ internal class ComponentInjectIntermediateNode : ExtensionIntermediateNode
throw new ArgumentNullException(nameof(context));
}
context.CodeWriter.WriteAutoPropertyDeclaration(
_injectedPropertyModifiers,
TypeName,
MemberName,
TypeSpan,
MemberSpan,
context,
defaultValue: true);
if (TypeName == string.Empty && TypeSpan.HasValue && !context.Options.DesignTime)
{
// if we don't even have a type name, just emit an empty mapped region so that intellisense still works
context.CodeWriter.BuildEnhancedLinePragma(TypeSpan.Value, context).Dispose();
}
else
{
var memberName = MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context);
if (!context.Options.DesignTime || !IsMalformed)
{
context.CodeWriter.WriteAutoPropertyDeclaration(
_injectedPropertyModifiers,
TypeName,
memberName,
TypeSpan,
MemberSpan,
context,
defaultValue: true);
}
}
}
}

Просмотреть файл

@ -101,12 +101,8 @@ internal sealed class DefaultTagHelperTargetExtension : IDefaultTagHelperTargetE
// Assign a unique ID for this instance of the source HTML tag. This must be unique
// per call site, e.g. if the tag is on the view twice, there should be two IDs.
var uniqueId = context.Options.SuppressUniqueIds;
if (uniqueId == null)
{
uniqueId = GetDeterministicId(context);
}
var uniqueId = GetDeterministicId(context);
context.CodeWriter.WriteStringLiteral(node.TagName)
.WriteParameterSeparator()
.Write(TagModeTypeName)
@ -650,10 +646,13 @@ internal sealed class DefaultTagHelperTargetExtension : IDefaultTagHelperTargetE
// Internal for testing
internal static string GetDeterministicId(CodeRenderingContext context)
{
// Use the file checksum along with the absolute position in the generated code to create a unique id for each tag helper call site.
var checksum = ChecksumUtilities.BytesToString(context.SourceDocument.Text.GetChecksum());
var uniqueId = checksum + context.CodeWriter.Location.AbsoluteIndex;
var uniqueId = context.Options.SuppressUniqueIds;
if (uniqueId is null)
{
// Use the file checksum along with the absolute position in the generated code to create a unique id for each tag helper call site.
var checksum = ChecksumUtilities.BytesToString(context.SourceDocument.Text.GetChecksum());
uniqueId = checksum + context.CodeWriter.Location.AbsoluteIndex;
}
return uniqueId;
}

Просмотреть файл

@ -108,6 +108,11 @@ public struct SourceSpan : IEquatable<SourceSpan>
endCharacterIndex);
}
internal readonly SourceSpan GetZeroWidthEndSpan()
{
return new SourceSpan(FilePath, AbsoluteIndex + EndCharacterIndex, LineIndex, characterIndex: EndCharacterIndex, length: 0, lineCount: 0, EndCharacterIndex);
}
public static bool operator ==(SourceSpan left, SourceSpan right)
{
return left.Equals(right);

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -55,18 +56,20 @@ public static class InjectDirective
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
continue;
}
var tokens = directive.Children.OfType<DirectiveTokenIntermediateNode>().ToArray();
var isMalformed = directive is MalformedDirectiveIntermediateNode;
var typeName = tokens[0].Content;
var typeSpan = tokens[0].Source;
var memberName = tokens[1].Content;
var memberSpan = tokens[1].Source;
var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content);
Debug.Assert(hasType || isMalformed);
var typeName = hasType ? tokens[0].Content : string.Empty;
var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan();
if (!properties.Add(memberName))
var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content);
Debug.Assert(hasMemberName || isMalformed);
var memberName = hasMemberName ? tokens[1].Content : null;
var memberSpan = hasMemberName ? tokens[1].Source : null;
if (hasMemberName && !properties.Add(memberName))
{
continue;
}
@ -86,7 +89,8 @@ public static class InjectDirective
TypeName = typeName,
MemberName = memberName,
TypeSource = typeSpan,
MemberSource = memberSpan
MemberSource = memberSpan,
IsMalformed = isMalformed
};
visitor.Class.Children.Add(injectNode);
@ -98,7 +102,7 @@ public static class InjectDirective
{
public ClassDeclarationIntermediateNode Class { get; private set; }
public IList<DirectiveIntermediateNode> Directives { get; } = new List<DirectiveIntermediateNode>();
public IList<IntermediateNode> Directives { get; } = [];
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
@ -117,5 +121,13 @@ public static class InjectDirective
Directives.Add(node);
}
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (node.Directive == Directive)
{
Directives.Add(node);
}
}
}
}

Просмотреть файл

@ -20,6 +20,8 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
public SourceSpan? MemberSource { get; set; }
public bool IsMalformed { get; set; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
@ -60,5 +62,6 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
formatter.WriteProperty(nameof(MemberName), MemberName);
formatter.WriteProperty(nameof(TypeName), TypeName);
formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString());
}
}

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
@ -26,10 +27,19 @@ public class InjectTargetExtension : IInjectTargetExtension
if (!context.Options.DesignTime && !string.IsNullOrWhiteSpace(node.TypeSource?.FilePath))
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, node.MemberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
if (node.TypeName == "")
{
// if we don't even have a type name, just emit an empty mapped region so that intellisense still works
context.CodeWriter.BuildEnhancedLinePragma(node.TypeSource.Value, context).Dispose();
}
else
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
}
}
else
else if (!node.IsMalformed)
{
var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}";

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -55,18 +56,20 @@ public static class InjectDirective
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
continue;
}
var tokens = directive.Children.OfType<DirectiveTokenIntermediateNode>().ToArray();
var isMalformed = directive is MalformedDirectiveIntermediateNode;
var typeName = tokens[0].Content;
var typeSpan = tokens[0].Source;
var memberName = tokens[1].Content;
var memberSpan = tokens[1].Source;
var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content);
Debug.Assert(hasType || isMalformed);
var typeName = hasType ? tokens[0].Content : string.Empty;
var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan();
if (!properties.Add(memberName))
var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content);
Debug.Assert(hasMemberName || isMalformed);
var memberName = hasMemberName ? tokens[1].Content : null;
var memberSpan = hasMemberName ? tokens[1].Source : null;
if (hasMemberName && !properties.Add(memberName))
{
continue;
}
@ -86,7 +89,8 @@ public static class InjectDirective
TypeName = typeName,
MemberName = memberName,
TypeSource = typeSpan,
MemberSource = memberSpan
MemberSource = memberSpan,
IsMalformed = isMalformed
};
visitor.Class.Children.Add(injectNode);
@ -98,7 +102,7 @@ public static class InjectDirective
{
public ClassDeclarationIntermediateNode Class { get; private set; }
public IList<DirectiveIntermediateNode> Directives { get; } = new List<DirectiveIntermediateNode>();
public IList<IntermediateNode> Directives { get; } = [];
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
@ -117,5 +121,13 @@ public static class InjectDirective
Directives.Add(node);
}
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (node.Directive == Directive)
{
Directives.Add(node);
}
}
}
}

Просмотреть файл

@ -20,6 +20,8 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
public SourceSpan? MemberSource { get; set; }
public bool IsMalformed { get; set; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
@ -60,5 +62,6 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
formatter.WriteProperty(nameof(MemberName), MemberName);
formatter.WriteProperty(nameof(TypeName), TypeName);
formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString());
}
}

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X;
@ -26,10 +27,19 @@ public class InjectTargetExtension : IInjectTargetExtension
if (!context.Options.DesignTime && !string.IsNullOrWhiteSpace(node.TypeSource?.FilePath))
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, node.MemberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
if (node.TypeName == "")
{
// if we don't even have a type name, just emit an empty mapped region so that intellisense still works
context.CodeWriter.BuildEnhancedLinePragma(node.TypeSource.Value, context).Dispose();
}
else
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
}
}
else
else if(!node.IsMalformed)
{
var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}";

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -62,18 +63,20 @@ public static class InjectDirective
for (var i = visitor.Directives.Count - 1; i >= 0; i--)
{
var directive = visitor.Directives[i];
var tokens = directive.Tokens.ToArray();
if (tokens.Length < 2)
{
continue;
}
var tokens = directive.Children.OfType<DirectiveTokenIntermediateNode>().ToArray();
var isMalformed = directive is MalformedDirectiveIntermediateNode;
var typeName = tokens[0].Content;
var typeSpan = tokens[0].Source;
var memberName = tokens[1].Content;
var memberSpan = tokens[1].Source;
var hasType = tokens.Length > 0 && !string.IsNullOrWhiteSpace(tokens[0].Content);
Debug.Assert(hasType || isMalformed);
var typeName = hasType ? tokens[0].Content : string.Empty;
var typeSpan = hasType ? tokens[0].Source : directive.Source?.GetZeroWidthEndSpan();
if (!properties.Add(memberName))
var hasMemberName = tokens.Length > 1 && !string.IsNullOrWhiteSpace(tokens[1].Content);
Debug.Assert(hasMemberName || isMalformed);
var memberName = hasMemberName ? tokens[1].Content : null;
var memberSpan = hasMemberName ? tokens[1].Source : null;
if (hasMemberName && !properties.Add(memberName))
{
continue;
}
@ -93,7 +96,8 @@ public static class InjectDirective
TypeName = typeName,
MemberName = memberName,
TypeSource = typeSpan,
MemberSource = memberSpan
MemberSource = memberSpan,
IsMalformed = isMalformed
};
visitor.Class.Children.Add(injectNode);
@ -105,7 +109,7 @@ public static class InjectDirective
{
public ClassDeclarationIntermediateNode Class { get; private set; }
public IList<DirectiveIntermediateNode> Directives { get; } = new List<DirectiveIntermediateNode>();
public IList<IntermediateNode> Directives { get; } = [];
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
@ -124,5 +128,13 @@ public static class InjectDirective
Directives.Add(node);
}
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (node.Directive == Directive)
{
Directives.Add(node);
}
}
}
}

Просмотреть файл

@ -20,6 +20,8 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
public SourceSpan? MemberSource { get; set; }
public bool IsMalformed { get; set; }
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
@ -60,5 +62,6 @@ public class InjectIntermediateNode : ExtensionIntermediateNode
formatter.WriteProperty(nameof(MemberName), MemberName);
formatter.WriteProperty(nameof(TypeName), TypeName);
formatter.WriteProperty(nameof(IsMalformed), IsMalformed.ToString());
}
}

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Extensions;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions;
@ -26,10 +27,19 @@ public class InjectTargetExtension : IInjectTargetExtension
if (!context.Options.DesignTime && !string.IsNullOrWhiteSpace(node.TypeSource?.FilePath))
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, node.MemberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
if (node.TypeName == "")
{
// if we don't even have a type name, just emit an empty mapped region so that intellisense still works
context.CodeWriter.BuildEnhancedLinePragma(node.TypeSource.Value, context).Dispose();
}
else
{
context.CodeWriter.WriteLine(RazorInjectAttribute);
var memberName = node.MemberName ?? "Member_" + DefaultTagHelperTargetExtension.GetDeterministicId(context);
context.CodeWriter.WriteAutoPropertyDeclaration(["public"], node.TypeName, memberName, node.TypeSource, node.MemberSource, context, privateSetter: true, defaultValue: true);
}
}
else
else if (!node.IsMalformed)
{
var property = $"public {node.TypeName} {node.MemberName} {{ get; private set; }}";
if (!context.Options.SuppressNullabilityEnforcement)

Просмотреть файл

@ -122,6 +122,8 @@ public class RazorIntegrationTestBase
b.Features.Add(new SetNewLineOptionFeature(LineEnding));
}
b.Features.Add(new SuppressUniqueIdsPhase());
b.Features.Add(new CompilationTagHelperFeature());
b.Features.Add(new DefaultMetadataReferenceFeature()
{
@ -497,6 +499,16 @@ public class RazorIntegrationTestBase
}
}
private sealed class SuppressUniqueIdsPhase : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
public int Order { get; }
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
options.SuppressUniqueIds = "__UniqueIdSuppressedForTesting__";
}
}
private class TestImportProjectFeature : IImportProjectFeature
{
private readonly List<RazorProjectItem> _imports;