Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,51 @@ public void AddDefaultDirectivesImport_AddsSingleDynamicImport()
using var imports = new PooledArrayBuilder<RazorProjectItem>();

// Act
MvcImportProjectFeature.AddDefaultDirectivesImport(ref imports.AsRef());
MvcImportProjectFeature.AddDefaultDirectivesImport(suppressMvcRazorImports: false, ref imports.AsRef());

// Assert
var import = Assert.Single(imports.ToImmutable());
Assert.Null(import.FilePath);
}

[Fact]
public void AddDefaultDirectivesImport_DefaultImportContainsMvcDirectives()
{
// Arrange
using var imports = new PooledArrayBuilder<RazorProjectItem>();

// Act
MvcImportProjectFeature.AddDefaultDirectivesImport(suppressMvcRazorImports: false, ref imports.AsRef());

// Assert
var import = Assert.Single(imports.ToImmutable());
var content = ReadContent(import);
Assert.Contains("@inject", content);
Assert.Contains("@addTagHelper", content);
Assert.Contains("@using global::Microsoft.AspNetCore.Mvc", content);
}

[Fact]
public void AddDefaultDirectivesImport_SuppressMvcRazorImports_OmitsMvcDirectives()
{
// Arrange
using var imports = new PooledArrayBuilder<RazorProjectItem>();

// Act
MvcImportProjectFeature.AddDefaultDirectivesImport(suppressMvcRazorImports: true, ref imports.AsRef());

// Assert
var import = Assert.Single(imports.ToImmutable());
var content = ReadContent(import);
Assert.DoesNotContain("@inject", content);
Assert.DoesNotContain("@addTagHelper", content);
Assert.DoesNotContain("Microsoft.AspNetCore.Mvc", content);
Assert.Contains("@using global::System", content);
Assert.Contains("@using global::System.Collections.Generic", content);
Assert.Contains("@using global::System.Linq", content);
Assert.Contains("@using global::System.Threading.Tasks", content);
}

[Fact]
public void AddHierarchicalImports_AddsViewImportSourceDocumentsOnDisk()
{
Expand Down Expand Up @@ -71,4 +109,11 @@ public void AddHierarchicalImports_AddsViewImportSourceDocumentsNotOnDisk()
import => Assert.Equal("/Pages/_ViewImports.cshtml", import.FilePath),
import => Assert.Equal("/Pages/Contact/_ViewImports.cshtml", import.FilePath));
}

private static string ReadContent(RazorProjectItem item)
{
using var stream = item.Read();
using var reader = new System.IO.StreamReader(stream);
return reader.ReadToEnd();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public sealed record class RazorConfiguration(
bool UseConsolidatedMvcViews = true,
bool SuppressAddComponentParameter = false,
bool UseRoslynTokenizer = false,
bool SuppressMvcRazorImports = false,
ImmutableArray<string> PreprocessorSymbols = default)
{
public ImmutableArray<string> PreprocessorSymbols
Expand All @@ -32,6 +33,7 @@ public ImmutableArray<string> PreprocessorSymbols
UseConsolidatedMvcViews: true,
SuppressAddComponentParameter: false,
UseRoslynTokenizer: false,
SuppressMvcRazorImports: false,
PreprocessorSymbols: []);

public bool Equals(RazorConfiguration? other)
Expand All @@ -42,6 +44,7 @@ public bool Equals(RazorConfiguration? other)
SuppressAddComponentParameter == other.SuppressAddComponentParameter &&
UseConsolidatedMvcViews == other.UseConsolidatedMvcViews &&
UseRoslynTokenizer == other.UseRoslynTokenizer &&
SuppressMvcRazorImports == other.SuppressMvcRazorImports &&
PreprocessorSymbols.SequenceEqual(other.PreprocessorSymbols) &&
Extensions.SequenceEqual(other.Extensions);

Expand All @@ -55,6 +58,7 @@ public override int GetHashCode()
hash.Add(SuppressAddComponentParameter);
hash.Add(UseConsolidatedMvcViews);
hash.Add(UseRoslynTokenizer);
hash.Add(SuppressMvcRazorImports);
hash.Add(PreprocessorSymbols);
return hash;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ internal sealed class MvcImportProjectFeature : RazorProjectEngineFeatureBase, I
@addTagHelper global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
@addTagHelper global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
@addTagHelper global::Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
");

private static readonly DefaultImportProjectItem s_defaultImportNoMvc = new($"Default non-MVC imports ({ImportsFileName})", @"
@using global::System
@using global::System.Collections.Generic
@using global::System.Linq
@using global::System.Threading.Tasks
");

public void CollectImports(RazorProjectItem projectItem, ref PooledArrayBuilder<RazorProjectItem> imports)
Expand All @@ -39,16 +46,16 @@ public void CollectImports(RazorProjectItem projectItem, ref PooledArrayBuilder<
return;
}

AddDefaultDirectivesImport(ref imports);
AddDefaultDirectivesImport(ProjectEngine.Configuration.SuppressMvcRazorImports, ref imports);

// We add hierarchical imports second so any default directive imports can be overridden.
AddHierarchicalImports(projectItem, ref imports);
}

// Internal for testing
internal static void AddDefaultDirectivesImport(ref PooledArrayBuilder<RazorProjectItem> imports)
internal static void AddDefaultDirectivesImport(bool suppressMvcRazorImports, ref PooledArrayBuilder<RazorProjectItem> imports)
{
imports.Add(s_defaultImport);
imports.Add(suppressMvcRazorImports ? s_defaultImportNoMvc : s_defaultImport);
}

// Internal for testing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public partial class RazorSourceGenerator
globalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace);
globalOptions.TryGetValue("build_property.SupportLocalizedComponentNames", out var supportLocalizedComponentNames);
globalOptions.TryGetValue("build_property.GenerateRazorMetadataSourceChecksumAttributes", out var generateMetadataSourceChecksumAttributes);
globalOptions.TryGetValue("build_property.SuppressMvcRazorImports", out var suppressMvcRazorImports);

Diagnostic? diagnostic = null;
if (!globalOptions.TryGetValue("build_property.RazorLangVersion", out var razorLanguageVersionString) ||
Expand All @@ -54,7 +55,7 @@ public partial class RazorSourceGenerator
? false
: CSharpCompilation.Create("components", references: minimalReferences).HasAddComponentParameter();

var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported);
var razorConfiguration = new RazorConfiguration(razorLanguageVersion, configurationName ?? "default", Extensions: [], UseConsolidatedMvcViews: true, SuppressAddComponentParameter: !isComponentParameterSupported, SuppressMvcRazorImports: suppressMvcRazorImports == "true");

// We use the new tokenizer only when requested for now.
var useRoslynTokenizer = parseOptions.UseRoslynTokenizer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,51 @@ internal sealed class Views_Shared__Layout : global::Microsoft.AspNetCore.Mvc.Ra

}

[Fact, WorkItem("https://github.com/dotnet/razor/issues/8259")]
public async Task SourceGenerator_CshtmlFiles_SuppressMvcRazorImports()
{
// Arrange
var project = CreateTestProject(new()
{
["Pages/Index.cshtml"] = "<h1>Hello world</h1>",
});
var compilation = await project.GetCompilationAsync();
var (driver, _, _) = await GetDriverWithAdditionalTextAndProviderAsync(project, options =>
{
options.TestGlobalOptions["build_property.SuppressMvcRazorImports"] = "true";
});

// Act
var result = RunGenerator(compilation!, ref driver);

// Assert
Assert.Empty(result.Diagnostics);
var generatedSource = Assert.Single(result.GeneratedSources);
var generatedCode = generatedSource.SourceText.ToString();

// Should NOT contain MVC-specific inject properties
Assert.DoesNotContain("RazorInjectAttribute", generatedCode);
Assert.DoesNotContain("IModelExpressionProvider", generatedCode);
Assert.DoesNotContain("IUrlHelper", generatedCode);
Assert.DoesNotContain("IViewComponentHelper", generatedCode);
Assert.DoesNotContain("IJsonHelper", generatedCode);
Assert.DoesNotContain("IHtmlHelper", generatedCode);

// Should NOT contain MVC-specific using directives
Assert.DoesNotContain("using global::Microsoft.AspNetCore.Mvc;", generatedCode);
Assert.DoesNotContain("using global::Microsoft.AspNetCore.Mvc.Rendering;", generatedCode);
Assert.DoesNotContain("using global::Microsoft.AspNetCore.Mvc.ViewFeatures;", generatedCode);

// Should still contain System using directives
Assert.Contains("using global::System;", generatedCode);
Assert.Contains("using global::System.Collections.Generic;", generatedCode);
Assert.Contains("using global::System.Linq;", generatedCode);
Assert.Contains("using global::System.Threading.Tasks;", generatedCode);

// Should still contain the page content
Assert.Contains("Hello world", generatedCode);
}

[Fact, WorkItem("https://github.com/dotnet/razor/issues/7049")]
public async Task SourceGenerator_CshtmlFiles_TagHelperInFunction()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Razor.Serialization.MessagePack.Formatters;

internal sealed class RazorConfigurationFormatter : ValueFormatter<RazorConfiguration>
{
private const int SerializerPropertyCount = 7;
private const int SerializerPropertyCount = 8;

public static readonly ValueFormatter<RazorConfiguration> Instance = new RazorConfigurationFormatter();

Expand All @@ -31,6 +31,7 @@ public override RazorConfiguration Deserialize(ref MessagePackReader reader, Ser
var suppressAddComponentParameter = reader.ReadBoolean();
var useConsolidatedMvcViews = reader.ReadBoolean();
var useRoslynTokenizer = reader.ReadBoolean();
var suppressMvcRazorImports = reader.ReadBoolean();
var preprocessorSymbols = reader.Deserialize<ImmutableArray<string>>(options);

count -= SerializerPropertyCount;
Expand All @@ -57,6 +58,7 @@ public override RazorConfiguration Deserialize(ref MessagePackReader reader, Ser
UseConsolidatedMvcViews: useConsolidatedMvcViews,
SuppressAddComponentParameter: suppressAddComponentParameter,
UseRoslynTokenizer: useRoslynTokenizer,
SuppressMvcRazorImports: suppressMvcRazorImports,
PreprocessorSymbols: preprocessorSymbols);
}

Expand All @@ -83,6 +85,7 @@ public override void Serialize(ref MessagePackWriter writer, RazorConfiguration
writer.Write(value.SuppressAddComponentParameter);
writer.Write(value.UseConsolidatedMvcViews);
writer.Write(value.UseRoslynTokenizer);
writer.Write(value.SuppressMvcRazorImports);
writer.Serialize(value.PreprocessorSymbols, options);

count -= SerializerPropertyCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public static RazorConfiguration ComputeRazorConfigurationOptions(Project projec

var suppressAddComponentParameter = compilation is not null && !compilation.HasAddComponentParameter();

globalOptions.TryGetValue("build_property.SuppressMvcRazorImports", out var suppressMvcRazorImportsValue);

var csharpParseOptions = project.ParseOptions as CSharpParseOptions ?? CSharpParseOptions.Default;

var razorConfiguration = new RazorConfiguration(
Expand All @@ -132,6 +134,7 @@ public static RazorConfiguration ComputeRazorConfigurationOptions(Project projec
UseConsolidatedMvcViews: true,
suppressAddComponentParameter,
UseRoslynTokenizer: csharpParseOptions.UseRoslynTokenizer(),
SuppressMvcRazorImports: suppressMvcRazorImportsValue == "true",
PreprocessorSymbols: csharpParseOptions.PreprocessorSymbolNames.ToImmutableArray());

defaultNamespace = rootNamespace ?? "ASP"; // TODO: Source generator does this. Do we want it?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public void RazorConfigurationJsonConverter_Serialization_CanRoundTrip()
UseConsolidatedMvcViews: false,
SuppressAddComponentParameter: true,
UseRoslynTokenizer: true,
SuppressMvcRazorImports: true,
PreprocessorSymbols: ["DEBUG", "TRACE", "DAVID"]);

// Act
Expand All @@ -42,6 +43,7 @@ public void RazorConfigurationJsonConverter_Serialization_CanRoundTrip()
Assert.Equal(configuration.UseConsolidatedMvcViews, obj.UseConsolidatedMvcViews);
Assert.Equal(configuration.SuppressAddComponentParameter, obj.SuppressAddComponentParameter);
Assert.Equal(configuration.UseRoslynTokenizer, obj.UseRoslynTokenizer);
Assert.Equal(configuration.SuppressMvcRazorImports, obj.SuppressMvcRazorImports);
Assert.Collection(obj.PreprocessorSymbols,
s => Assert.Equal("DEBUG", s),
s => Assert.Equal("TRACE", s),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static RazorConfiguration ReadConfigurationFromProperties(JsonDataReader
var suppressAddComponentParameter = reader.ReadBooleanOrFalse(nameof(RazorConfiguration.SuppressAddComponentParameter));
var useConsolidatedMvcViews = reader.ReadBooleanOrTrue(nameof(RazorConfiguration.UseConsolidatedMvcViews));
var useRoslynTokenizer = reader.ReadBooleanOrFalse(nameof(RazorConfiguration.UseRoslynTokenizer));
var suppressMvcRazorImports = reader.ReadBooleanOrFalse(nameof(RazorConfiguration.SuppressMvcRazorImports));
var preprocessorSymbols = reader.ReadImmutableArrayOrEmpty(nameof(RazorConfiguration.PreprocessorSymbols), r => r.ReadNonNullString());
var extensions = reader.ReadImmutableArrayOrEmpty(nameof(RazorConfiguration.Extensions),
static r =>
Expand All @@ -48,6 +49,7 @@ public static RazorConfiguration ReadConfigurationFromProperties(JsonDataReader
UseConsolidatedMvcViews: useConsolidatedMvcViews,
SuppressAddComponentParameter: suppressAddComponentParameter,
UseRoslynTokenizer: useRoslynTokenizer,
SuppressMvcRazorImports: suppressMvcRazorImports,
PreprocessorSymbols: preprocessorSymbols);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static void WriteProperties(JsonDataWriter writer, RazorConfiguration val
writer.WriteIfNotFalse(nameof(value.SuppressAddComponentParameter), value.SuppressAddComponentParameter);
writer.WriteIfNotTrue(nameof(value.UseConsolidatedMvcViews), value.UseConsolidatedMvcViews);
writer.WriteIfNotFalse(nameof(value.UseRoslynTokenizer), value.UseRoslynTokenizer);
writer.WriteIfNotFalse(nameof(value.SuppressMvcRazorImports), value.SuppressMvcRazorImports);
writer.WriteArrayIfNotDefaultOrEmpty(nameof(value.PreprocessorSymbols), value.PreprocessorSymbols, static (w, v) => w.Write(v));

writer.WriteArrayIfNotDefaultOrEmpty(nameof(value.Extensions), value.Extensions, static (w, v) => w.Write(v.ExtensionName));
Expand Down