diff --git a/src/Razor/Directory.Build.props b/src/Razor/Directory.Build.props index d43f1c13df7e8..1737c22d8657d 100644 --- a/src/Razor/Directory.Build.props +++ b/src/Razor/Directory.Build.props @@ -9,9 +9,6 @@ false true false - - - diff --git a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentRenderModeDirectiveIntegrationTests.cs b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentRenderModeDirectiveIntegrationTests.cs index 698b9cf7e6f65..b5f429f5d8a66 100644 --- a/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentRenderModeDirectiveIntegrationTests.cs +++ b/src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentRenderModeDirectiveIntegrationTests.cs @@ -56,7 +56,7 @@ @rendermode Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer Diagnostic(ErrorCode.ERR_BadArity, "TestComponent").WithArguments("Test.TestComponent", "type", "1").WithLocation(13, 19)); } - [Fact(Skip = "PROTOTYPE(sonic): rendermode lowering doesn't emit a #line directive on the synthesized attribute decoration in the decl half, so the resulting diagnostic points at the generated file instead of the @rendermode token. Track + fix before merging to main. See PR #83887.")] + [Fact] public void RenderMode_GenericComponent_CSharp10() { var csharpParseOptions = CSharpParseOptions.WithLanguageVersion(CodeAnalysis.CSharp.LanguageVersion.CSharp10); @@ -69,17 +69,16 @@ @rendermode Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer """); CompileToAssembly(compilationResult, - // PROTOTYPE: this diagnostic should map back to the @rendermode directive in - // the source rather than the synthesized attribute decoration in the decl - // generated file. The decoration is added without a SourceSpan so the writer - // emits it without a #line directive. Track + fix before merging to main. + // @rendermode on a generic (@typeparam) component generates an attribute decoration that + // references the component without its type argument, plus a nested attribute type that needs + // generic-attribute support. This is a known limitation + // (https://github.com/dotnet/razor/issues/9683): the file-scoped attribute class generated for + // C# 11+ avoids it, but C# 10 still surfaces the raw C# errors against the generated code. // - // x:\dir\subdir\Test\TestComponent.cshtml.decl.g.cs(13,19): error CS0305: Using the generic type 'TestComponent' requires 1 type arguments - // [global::Test.TestComponent.__PrivateComponentRenderModeAttribute] + // x:\dir\subdir\Test\TestComponent.cshtml(13,19): error CS0305: Using the generic type 'TestComponent' requires 1 type arguments Diagnostic(ErrorCode.ERR_BadArity, "TestComponent").WithArguments("Test.TestComponent", "type", "1").WithLocation(13, 19), - // x:\dir\subdir\Test\TestComponent.cshtml(30,70): error CS8936: Feature 'generic attributes' is not available in C# 10.0. Please use language version 11.0 or greater. - // private sealed class __PrivateComponentRenderModeAttribute : global::Microsoft.AspNetCore.Components.RenderModeAttribute - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "global::Microsoft.AspNetCore.Components.RenderModeAttribute").WithArguments("generic attributes", "11.0").WithLocation(30, 70)); + // x:\dir\subdir\Test\TestComponent.cshtml(31,70): error CS8936: Feature 'generic attributes' is not available in C# 10.0. Please use language version 11.0 or greater. + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "global::Microsoft.AspNetCore.Components.RenderModeAttribute").WithArguments("generic attributes", "11.0").WithLocation(31, 70)); } [Fact] @@ -238,7 +237,7 @@ @rendermode Foo CompileToAssembly(compilationResult); } - [Fact(Skip = "PROTOTYPE(sonic): rendermode lowering doesn't emit a #line directive on the synthesized `=> ` arrow expression, so the diagnostic's line/column point inside the generated helper rather than at the user's @rendermode token. Track + fix before merging to main. See PR #83887.")] + [Fact] public void LanguageVersion_BreakingChange_8_0() { var compilationResult = CompileToCSharp(""" @@ -253,15 +252,15 @@ @rendermode Foo Assert.Empty(compilationResult.RazorDiagnostics); CompileToAssembly(compilationResult, - // PROTOTYPE: the rendermode lowering doesn't emit a #line directive on the - // synthesized `=> ` arrow expression, so this diagnostic's line/column - // point inside the generated helper rather than at the user's @rendermode - // token. Pre-existing limitation but worth fixing -- track + address before - // merging to main. + // On Razor < 11 the @rendermode expression isn't mapped back to source, so a diagnostic on the + // expression reports the generated line/column inside the synthesized ModeImpl helper rather + // than the @rendermode token. This can't be fixed retroactively for those language versions: + // changing the generated code now would make a newer compiler emit different output than the + // shipped VS tooling expects, which breaks hot reload. Mapping was only added for Razor 11+ + // (dotnet/razor#12604). // - // x:\dir\subdir\Test\TestComponent.cshtml(24,101): error CS0103: The name 'Foo' does not exist in the current context - // private static IComponentRenderMode ModeImpl => Foo; - Diagnostic(ErrorCode.ERR_NameNotInContext, "Foo").WithArguments("Foo").WithLocation(24, 101), + // x:\dir\subdir\Test\TestComponent.cshtml(25,101): error CS0103: The name 'Foo' does not exist in the current context + Diagnostic(ErrorCode.ERR_NameNotInContext, "Foo").WithArguments("Foo").WithLocation(25, 101), // x:\dir\subdir\Test\TestComponent.cshtml(5,12): warning CS0414: The field 'TestComponent.rendermode' is assigned but its value is never used // string rendermode = "Something"; Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "rendermode").WithArguments("Test.TestComponent.rendermode").WithLocation(5, 12) diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeDocument.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeDocument.cs index a07045af4979a..e6e0683698dec 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeDocument.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeDocument.cs @@ -284,27 +284,6 @@ internal RazorCodeDocument WithUnresolvedDocumentNode(DocumentIntermediateNode v internal RazorCSharpDocument GetRequiredCSharpDocument(bool declarationDocument) => GetCSharpDocument(declarationDocument).AssumeNotNull(); -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - internal bool TryGetImplCSharpDocument([NotNullWhen(true)] out RazorCSharpDocument? result) - { - result = _csharpDocument; - return result is not null; - } - -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - internal RazorCSharpDocument? GetImplCSharpDocument() - => _csharpDocument; - -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - internal RazorCSharpDocument GetRequiredImplCSharpDocument() - => _csharpDocument.AssumeNotNull(); - internal RazorCodeDocument WithImplCSharpDocument(RazorCSharpDocument value) { Debug.Assert(value is not null); @@ -315,12 +294,6 @@ internal RazorCodeDocument WithImplCSharpDocument(RazorCSharpDocument value) return new RazorCodeDocument(Source, Imports, ParserOptions, CodeGenerationOptions, _tagHelpers, _referencedTagHelpers, _syntaxTree, _tagHelperRewrittenSyntaxTree, _importSyntaxTrees, _tagHelperContext, _documentNode, value, _declCSharpDocument, _directiveTagHelperContributions, _unresolvedDocumentNode); } -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - internal RazorCSharpDocument? GetDeclCSharpDocument() - => _declCSharpDocument; - internal RazorCodeDocument WithDeclCSharpDocument(RazorCSharpDocument value) { Debug.Assert(value is not null); diff --git a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs index cf9b495691471..273ad807cf1bc 100644 --- a/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs +++ b/src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/RazorSourceGenerator.cs @@ -107,7 +107,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.FilePath); var declEngine = GetDeclarationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); - fallbackDecl = declEngine.Process(sourceItem, cancellationToken).GetRequiredImplCSharpDocument(); + fallbackDecl = declEngine.Process(sourceItem, cancellationToken).GetRequiredCSharpDocument(declarationDocument: false); fallbackTypeName = typeName; RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.FilePath); } @@ -129,7 +129,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var declSources = processedDocuments .Select(static (item, _) => { - var declCSharpDocument = item.document.CodeDocument.GetDeclCSharpDocument(); + var declCSharpDocument = item.document.CodeDocument.GetCSharpDocument(declarationDocument: true); return (hintName: GetIdentifierFromPath(item.path), declCSharpDocument); }) .Where(static item => item.declCSharpDocument is not null) @@ -423,8 +423,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return ( hintName: GetIdentifierFromPath(filePath), codeDocument: document.CodeDocument, - csharpDocument: document.CodeDocument.GetRequiredImplCSharpDocument(), - declCSharpDocument: document.CodeDocument.GetDeclCSharpDocument()); + csharpDocument: document.CodeDocument.GetRequiredCSharpDocument(declarationDocument: false), + declCSharpDocument: document.CodeDocument.GetCSharpDocument(declarationDocument: true)); }) .WithLambdaComparer(static (a, b) => { diff --git a/src/Razor/src/Compiler/perf/Microbenchmarks/CodeGenerationBenchmark.cs b/src/Razor/src/Compiler/perf/Microbenchmarks/CodeGenerationBenchmark.cs index 3151025b2e16c..a512440850193 100644 --- a/src/Razor/src/Compiler/perf/Microbenchmarks/CodeGenerationBenchmark.cs +++ b/src/Razor/src/Compiler/perf/Microbenchmarks/CodeGenerationBenchmark.cs @@ -37,7 +37,7 @@ public CodeGenerationBenchmark() public void CodeGeneration_Runtime_LargeStaticFile() { var codeDocument = ProjectEngine.Process(MSN); - var generated = codeDocument.GetRequiredImplCSharpDocument(); + var generated = codeDocument.GetRequiredCSharpDocument(declarationDocument: false); if (generated.Diagnostics.Length > 0) { diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs index 91cf6a09c83ee..48a09d1f2b446 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/ProjectExtensions.cs @@ -37,14 +37,6 @@ internal static class ProjectExtensions return generatedDocuments.SingleOrDefault(d => d.HintName == hintName); } -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - public static async Task TryGetSourceGeneratedDocumentForRazorDocumentAsync(this Project project, TextDocument razorDocument, CancellationToken cancellationToken) - { - return (await TryGetSourceGeneratedDocumentsForRazorDocumentAsync(project, razorDocument, cancellationToken).ConfigureAwait(false))?.ImplDoc; - } - /// /// Finds source generated documents by iterating through all of them. In OOP there are better options! /// diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions.cs b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions.cs index 214e03541b85e..dc600488eeddc 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions.cs +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions.cs @@ -29,12 +29,6 @@ public static bool TryGetSyntaxRoot(this RazorCodeDocument codeDocument, [NotNul public static Syntax.SyntaxNode GetRequiredSyntaxRoot(this RazorCodeDocument codeDocument) => codeDocument.GetRequiredTagHelperRewrittenSyntaxTree().Root; -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call GetRequiredCSharpDocument and use the Text property on that, to prove that you thought about which document to get")] -#endif - public static SourceText GetCSharpSourceText(this RazorCodeDocument document) - => document.GetRequiredImplCSharpDocument().Text; - /// /// Returns the generated that corresponds to the given /// generated-source hint name. For Razor components the generator can emit two halves: diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/IRazorEditServiceExtensions.cs b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/IRazorEditServiceExtensions.cs index f115c74931785..3a1edca949d69 100644 --- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/IRazorEditServiceExtensions.cs +++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/DocumentMapping/IRazorEditServiceExtensions.cs @@ -12,26 +12,6 @@ namespace Microsoft.CodeAnalysis.Remote.Razor.DocumentMapping; internal static class IRazorEditServiceExtensions { -#if SONICDEV - [System.Obsolete("PROTOTYPE(sonic): Call the overload that takes a bool to prove that you thought about which document to get")] -#endif - public static async Task> MapCSharpEditsAsync( - this IRazorEditService service, - ImmutableArray textChanges, - RemoteDocumentSnapshot snapshot, - CancellationToken cancellationToken) - { - var mappedChanges = await service.MapCSharpEditsAsync( - textChanges.SelectAsArray(static c => c.ToRazorTextChange()), - snapshot, - declarationDocument: false, - includeCSharpLanguageFeatureEdits: true, - directlyMappedEditFilter: null, - cancellationToken).ConfigureAwait(false); - - return mappedChanges.SelectAsArray(static c => c.ToTextChange()); - } - public static async Task> MapCSharpEditsAsync( this IRazorEditService service, ImmutableArray textChanges, diff --git a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs index 16b57886aeae1..7081cffaab4ae 100644 --- a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs +++ b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/ImplementInterfaceTests.cs @@ -289,7 +289,7 @@ public interface IDerived : IBase makeDiagnosticsRequest: true); } - [Fact(Skip = "PROTOTYPE(sonic): cohost ImplementInterface positions members mid-@code-block with the decl/impl split; see https://github.com/dotnet/roslyn/issues/84609")] + [Fact] public async Task ImplementInterface_Explicitly_PartialBaseImplementations_AddsDerivedMembers() { await VerifyCodeActionAsync( @@ -324,6 +324,9 @@ @implements IDerived int IBase.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } string IBase.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + string IDerived.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + int IDerived.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } event EventHandler IBase.Event1 { @@ -338,10 +341,6 @@ event EventHandler IBase.Event1 } } - int IDerived.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - string IDerived.Property1 { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - event EventHandler IDerived.Event1 { add diff --git a/src/Razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/RazorCodeDocumentTestExtensions.cs b/src/Razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/RazorCodeDocumentTestExtensions.cs new file mode 100644 index 0000000000000..c6faea5bee7ee --- /dev/null +++ b/src/Razor/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/RazorCodeDocumentTestExtensions.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Razor.Language; + +/// +/// Test-only conveniences for reaching the implementation or declaration half of a +/// by name. Production code calls +/// / +/// directly so the decl/impl choice +/// is spelled out at the call site; tests keep the shorter names, which live here rather than on the +/// production type. +/// +public static class RazorCodeDocumentTestExtensions +{ + public static RazorCSharpDocument? GetImplCSharpDocument(this RazorCodeDocument document) + => document.GetCSharpDocument(declarationDocument: false); + + public static RazorCSharpDocument GetRequiredImplCSharpDocument(this RazorCodeDocument document) + => document.GetRequiredCSharpDocument(declarationDocument: false); + + public static RazorCSharpDocument? GetDeclCSharpDocument(this RazorCodeDocument document) + => document.GetCSharpDocument(declarationDocument: true); +}