* Switch the new lexer off-by-default.

* Add a new document for the lexer breaking changes around pragmas.

* Update comment.

* Feedback

Co-authored-by: Jan Jones <jan.jones.cz@gmail.com>

---------

Co-authored-by: Jan Jones <jan.jones.cz@gmail.com>
This commit is contained in:
Fred Silberberg 2024-10-24 10:30:56 -07:00 коммит произвёл GitHub
Родитель c724539e1c
Коммит c20c1cd6f3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 221 добавлений и 10 удалений

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

@ -0,0 +1,56 @@
# This document lists known breaking changes in Razor after .NET 9 all the way to .NET 10.
## Preprocessor directive parsing breaks
***Introduced in VS 17.13p1 and .NET 9.0.200***
A new lexing mode was introduced for understanding the C# sections in razor files that brings increased compatibility with how C# is natively lexed. However, this
also brings some breaking changes to the Razor compiler's understanding of C# preprocessing directives, which previously did not work consistently. Directives are
now required to start at the beginning of a line in Razor files (only whitespace is allowed before them). Additionally, disabled sections are now properly disabled
by the Razor compiler when `#if` preprocessor blocks are considered inactive.
### Preprocessor blocks are required to start at the beginning of a line
```razor
@{ #if DEBUG /* Previously allowed, now triggers RZ1043 */ }
<div>test</div>
@{ #endif /* Previously allowed, now triggers RZ1043 */ }
```
To fix, move the directives to a new line. Only whitespace is allowed before the directive.
```razor
@{
#if DEBUG /* This is allowed */
}
<div>test</div>
@{
#endif /* This is allowed */
}
```
### Disabled blocks are now considered properly in the Razor compiler
Disabled blocks are now considered completely disabled by the Razor compiler, and no attempt to understand the block is made. When combined with the previous break,
this means that if an `#else`, `#elif`, or `#endif` was not at the start of a line (modulo whitespace), a larger section of the file will be considered disabled than
in older versions of the Razor compiler. To help diagnose potential breaks here, the Razor compiler will scan disabled text sections for potential misplaced preprocessor
directives and report a warning if one is encountered.
```razor
@{
#if false
}
This area is now properly considered disabled by the razor compiler, and no attempt to understand it as either C# or HTML is made. This
can cause changes to how the output is rendered from previous versions of the Razor compiler.
@{ #else
In previous versions of the Razor compiler, this directive would have been picked up. It is no longer picked up because it is not at
the start of a line. The Razor compiler will report a warning, RZ1044, to help diagnose any potential breaks in this area.
}
@{
#endif
}
```

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

@ -49,15 +49,15 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators
.Where(r => r.Display is { } display && display.EndsWith("Microsoft.AspNetCore.Components.dll", StringComparison.Ordinal))
.ToImmutableArray();
var isComponentParameterSupported = minimalReferences.Length == 0
? false
var isComponentParameterSupported = minimalReferences.Length == 0
? false
: CSharpCompilation.Create("components", references: minimalReferences).HasAddComponentParameter();
var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported);
// We use the new tokenizer by default
var useRazorTokenizer = !parseOptions.Features.TryGetValue("use-razor-tokenizer", out var useRazorTokenizerValue)
|| !string.Equals(useRazorTokenizerValue, "false", StringComparison.OrdinalIgnoreCase);
// We use the new tokenizer only when requested for now.
var useRoslynTokenizer = parseOptions.Features.TryGetValue("use-roslyn-tokenizer", out var useRoslynTokenizerValue)
&& string.Equals(useRoslynTokenizerValue, "true", StringComparison.OrdinalIgnoreCase);
var razorSourceGenerationOptions = new RazorSourceGenerationOptions()
{
@ -67,7 +67,7 @@ namespace Microsoft.NET.Sdk.Razor.SourceGenerators
SupportLocalizedComponentNames = supportLocalizedComponentNames == "true",
CSharpParseOptions = (CSharpParseOptions)parseOptions,
TestSuppressUniqueIds = _testSuppressUniqueIds,
UseRoslynTokenizer = useRazorTokenizer,
UseRoslynTokenizer = useRoslynTokenizer,
};
return (razorSourceGenerationOptions, diagnostic);

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

@ -3265,5 +3265,159 @@ namespace MyApp
e => Assert.Equal("DiscoverTagHelpersFromCompilationStart", e.EventName),
e => Assert.Equal("DiscoverTagHelpersFromCompilationStop", e.EventName));
}
[Theory]
[InlineData("true")]
[InlineData("True")]
[InlineData("TRUE")]
[InlineData("tRuE")]
public async Task RoslynTokenizerEnabledWithTrue(string value)
{
var parseOptions = CSharpParseOptions.Default.WithFeatures([new("use-roslyn-tokenizer", value)]);
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """"
<div>@("""
nested "
""")</div>
"""",
}, cSharpParseOptions: parseOptions);
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver);
result.VerifyPageOutput(
""""
#pragma checksum "Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c6855f3cabbcb69477e3f5a61f8d77fcfed086c2"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenElement(0, "div");
__builder.AddContent(1,
#nullable restore
#line (1,8)-(3,8) "Pages/Index.razor"
"""
nested "
"""
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"""");
}
[Theory]
[InlineData("false")]
[InlineData("False")]
[InlineData("FALSE")]
[InlineData("FaLsE")]
[InlineData("")]
[InlineData(null)]
public async Task RoslynTokenizerDisabledWithFalseOrNothing(string? value)
{
var parseOptions = CSharpParseOptions.Default;
if (value != null)
{
parseOptions = parseOptions.WithFeatures([new("use-roslyn-tokenizer", value)]);
}
var project = CreateTestProject(new()
{
["Pages/Index.razor"] = """"
<div>@("""
nested "
""")</div>
"""",
}, cSharpParseOptions: parseOptions);
var compilation = await project.GetCompilationAsync();
var (driver, additionalTexts) = await GetDriverWithAdditionalTextAsync(project);
var result = RunGenerator(compilation!, ref driver,
// Pages/Index.razor(3,10): error CS1525: Invalid expression term '/'
// """)</div>
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "/").WithArguments("/").WithLocation(3, 10),
// Pages/Index.razor(3,11): error CS0103: The name 'div' does not exist in the current context
// """)</div>
Diagnostic(ErrorCode.ERR_NameNotInContext, "div").WithArguments("div").WithLocation(3, 11),
// Pages/Index.razor(3,15): error CS1525: Invalid expression term ')'
// """)</div>
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "").WithArguments(")").WithLocation(3, 15),
// Pages/Index.razor(3,15): error CS1002: ; expected
// """)</div>
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(3, 15),
// Pages/Index.razor(3,15): error CS1513: } expected
// """)</div>
Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(3, 15));
result.VerifyPageOutput(
""""
#pragma checksum "Pages/Index.razor" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "c6855f3cabbcb69477e3f5a61f8d77fcfed086c2"
// <auto-generated/>
#pragma warning disable 1591
namespace MyApp.Pages
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class Index : global::Microsoft.AspNetCore.Components.ComponentBase
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenElement(0, "div");
__builder.AddContent(1,
#nullable restore
#line (1,8)-(3,15) "Pages/Index.razor"
"""
nested "
""")</div>
#line default
#line hidden
#nullable disable
);
__builder.CloseElement();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
"""");
}
}
}

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

@ -230,9 +230,10 @@ public abstract class RazorSourceGeneratorTestsBase
protected static Project CreateTestProject(
OrderedStringDictionary additionalSources,
OrderedStringDictionary? sources = null)
OrderedStringDictionary? sources = null,
CSharpParseOptions? cSharpParseOptions = null)
{
var project = CreateBaseProject();
var project = CreateBaseProject(cSharpParseOptions);
if (sources is not null)
{
@ -294,7 +295,7 @@ public abstract class RazorSourceGeneratorTestsBase
}
}
private static Project CreateBaseProject()
private static Project CreateBaseProject(CSharpParseOptions? cSharpParseOptions)
{
var projectId = ProjectId.CreateNewId(debugName: "TestProject");
@ -314,7 +315,7 @@ public abstract class RazorSourceGeneratorTestsBase
new("CS8019", ReportDiagnostic.Suppress),
}));
project = project.WithParseOptions(((CSharpParseOptions)project.ParseOptions!).WithLanguageVersion(LanguageVersion.Preview));
project = project.WithParseOptions(cSharpParseOptions ?? ((CSharpParseOptions)project.ParseOptions!).WithLanguageVersion(LanguageVersion.Preview));
foreach (var defaultCompileLibrary in DependencyContext.Load(typeof(RazorSourceGeneratorTests).Assembly)!.CompileLibraries)
{