Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add <inheritdoc /> in migrations #27268

Merged
merged 1 commit into from
Jan 28, 2022
Merged
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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
<MicrosoftExtensionsLoggingVersion>7.0.0-alpha.1.22066.4</MicrosoftExtensionsLoggingVersion>
</PropertyGroup>
<PropertyGroup Label="Other dependencies">
<MicrosoftCodeAnalysisVersion>3.7.0</MicrosoftCodeAnalysisVersion>
<MicrosoftCodeAnalysisVersion>4.0.1</MicrosoftCodeAnalysisVersion>
Copy link
Contributor

@bricelam bricelam Jan 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move the following comment to here?

<!-- NB: Version affects the minimum required Visual Studio version -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" PrivateAssets="all" />

Copy link
Contributor

@bricelam bricelam Jan 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, I was too slow to comment; you already merged

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry... Can slip it into the next thing (either you or me).

Any idea what 4.0.1 restricts us to in terms of VS?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2022 (probably worth putting https://aka.ms/roslyn-packages in the comment)

</PropertyGroup>
</Project>
4 changes: 2 additions & 2 deletions src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ private static void AnalyzeNode(OperationAnalysisContext context)
case OperationKind.MethodReference:
AnalyzeMember(context, ((IMethodReferenceOperation)context.Operation).Method);
break;
case OperationKind.ObjectCreation:
AnalyzeMember(context, ((IObjectCreationOperation)context.Operation).Constructor);
case OperationKind.ObjectCreation when ((IObjectCreationOperation)context.Operation).Constructor is { } constructor:
AnalyzeMember(context, constructor);
break;
case OperationKind.Invocation:
AnalyzeInvocation(context, (IInvocationOperation)context.Operation);
Expand Down
27 changes: 27 additions & 0 deletions src/EFCore.Design/Migrations/Design/CSharpMigrationsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public override string GenerateMigration(
.AppendLine()
.AppendLine("#nullable disable");

// Suppress "Prefer jagged arrays over multidimensional" when we have a seeding operation with a multidimensional array
if (HasMultidimensionalArray(upOperations.Concat(downOperations)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this related to inheritdoc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't - it's another thing in the same area that's tracked by that same issue (see #10695 (comment)). Do you prefer I split it out, or think we shouldn't do it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should at least discuss. I really hate adding suppressions, especially for overzealous warnings like this. Is this a modern analyzer warning? If so, what is the justification for it? If it's an old FxCop warning then...

Copy link
Member Author

@roji roji Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule is reported by the standard .NET analyzers that started shipping as part of the SDK in .NET 5.0 (see docs). It's indeed not enabled by default, but anyone enabling e.g. all the "performance" category diagnostics gets it.

I also hate adding suppressions, which is why I went to the extra trouble of adding it only where we actually generate a multidimensional array. I do think it reduces a bit of needless friction for users who enable most of the rules, but if you're against it we can discuss and remove.

{
builder
.AppendLine()
.AppendLine("#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional");
}

if (!string.IsNullOrEmpty(migrationNamespace))
{
builder
Expand All @@ -88,11 +96,13 @@ public override string GenerateMigration(
}

builder
.AppendLine("/// <inheritdoc />")
.Append("public partial class ").Append(Code.Identifier(migrationName)).AppendLine(" : Migration")
.AppendLine("{");
using (builder.Indent())
{
builder
.AppendLine("/// <inheritdoc />")
.AppendLine("protected override void Up(MigrationBuilder migrationBuilder)")
.AppendLine("{");
using (builder.Indent())
Expand All @@ -104,6 +114,7 @@ public override string GenerateMigration(
.AppendLine()
.AppendLine("}")
.AppendLine()
.AppendLine("/// <inheritdoc />")
.AppendLine("protected override void Down(MigrationBuilder migrationBuilder)")
.AppendLine("{");
using (builder.Indent())
Expand Down Expand Up @@ -191,6 +202,7 @@ public override string GenerateMetadata(
using (builder.Indent())
{
builder
.AppendLine("/// <inheritdoc />")
.AppendLine("protected override void BuildTargetModel(ModelBuilder modelBuilder)")
.AppendLine("{")
.DecrementIndent()
Expand Down Expand Up @@ -313,4 +325,19 @@ public override string GenerateSnapshot(

return builder.ToString();
}

private bool HasMultidimensionalArray(IEnumerable<MigrationOperation> operations)
{
return operations.Any(
o =>
(o is InsertDataOperation insertDataOperation
&& IsMultidimensional(insertDataOperation.Values))
|| (o is UpdateDataOperation updateDataOperation
&& (IsMultidimensional(updateDataOperation.Values) || IsMultidimensional(updateDataOperation.KeyValues)))
|| (o is DeleteDataOperation deleteDataOperation
&& IsMultidimensional(deleteDataOperation.KeyValues)));

static bool IsMultidimensional(Array array)
=> array.GetLength(0) > 1 && array.GetLength(1) > 1;
}
}
1 change: 1 addition & 0 deletions src/Shared/ReferenceEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.CompilerServices;

#nullable enable
#pragma warning disable RS1024 // Compare symbols correctly (for Roslyn analyzers only)

namespace Microsoft.EntityFrameworkCore.Internal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,10 @@ public void Migrations_compile()

namespace MyNamespace
{
/// <inheritdoc />
public partial class MyMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(""-- TEST"")
Expand All @@ -523,6 +525,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
values: new object[] { 1, null, -1 });
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{

Expand Down Expand Up @@ -571,6 +574,7 @@ namespace MyNamespace
[Migration(""20150511161616_MyMigration"")]
partial class MyMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
Expand Down Expand Up @@ -608,7 +612,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
BuildReference.ByName("Microsoft.EntityFrameworkCore"),
BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational")
},
Sources = { { "Migration.cs", migrationCode }, { "MigrationSnapshot.cs", migrationMetadataCode } }
Sources = { { "Migration.cs", migrationCode }, { "MigrationSnapshot.cs", migrationMetadataCode } },
EmitDocumentationDiagnostics = true
};

var assembly = build.BuildInMemory();
Expand Down Expand Up @@ -1006,6 +1011,50 @@ public void Namespaces_imported_for_delete_data()
Assert.Contains("using System.Text.RegularExpressions;", migration);
}

[ConditionalFact]
public void Multidimensional_array_warning_is_suppressed_for_multidimensional_seed_data()
{
var generator = CreateMigrationsCodeGenerator();

var migration = generator.GenerateMigration(
"MyNamespace",
"MyMigration",
new[]
{
new DeleteDataOperation
{
Table = "MyTable",
KeyColumns = new[] { "Id" },
KeyValues = new object[,] { { 1, 2 }, { 3, 4 } }
}
},
Array.Empty<MigrationOperation>());

Assert.Contains("#pragma warning disable CA1814", migration);
}

[ConditionalFact]
public void Multidimensional_array_warning_is_not_suppressed_for_unidimensional_seed_data()
{
var generator = CreateMigrationsCodeGenerator();

var migration = generator.GenerateMigration(
"MyNamespace",
"MyMigration",
new[]
{
new DeleteDataOperation
{
Table = "MyTable",
KeyColumns = new[] { "Id" },
KeyValues = new object[,] { { 1, 2 } }
}
},
Array.Empty<MigrationOperation>());

Assert.DoesNotContain("#pragma warning disable CA1814", migration);
}

private static IMigrationsCodeGenerator CreateMigrationsCodeGenerator()
{
var testAssembly = typeof(CSharpMigrationsGeneratorTest).Assembly;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class BuildSource
public string TargetDir { get; set; }
public Dictionary<string, string> Sources { get; set; } = new();
public bool NullableReferenceTypes { get; set; }
public bool EmitDocumentationDiagnostics { get; set; }

public BuildFileResult Build()
{
Expand All @@ -46,7 +47,13 @@ public BuildFileResult Build()

var compilation = CSharpCompilation.Create(
projectName,
Sources.Select(s => SyntaxFactory.ParseSyntaxTree(s.Value).WithFilePath(s.Key)),
Sources.Select(
s => SyntaxFactory.ParseSyntaxTree(
text: s.Value,
path: s.Key,
options: new CSharpParseOptions(
LanguageVersion.Latest,
EmitDocumentationDiagnostics ? DocumentationMode.Diagnose : DocumentationMode.Parse))),
references,
CreateOptions());

Expand Down Expand Up @@ -78,18 +85,40 @@ public Assembly BuildInMemory()

var compilation = CSharpCompilation.Create(
projectName,
Sources.Select(s => SyntaxFactory.ParseSyntaxTree(s.Value).WithFilePath(s.Key)),
Sources.Select(
s => SyntaxFactory.ParseSyntaxTree(
text: s.Value,
path: s.Key,
options: new CSharpParseOptions(
LanguageVersion.Latest,
EmitDocumentationDiagnostics ? DocumentationMode.Diagnose : DocumentationMode.Parse))),
references,
CreateOptions());

var diagnostics = compilation.GetDiagnostics();
if (!diagnostics.IsEmpty)
{
throw new InvalidOperationException(
$@"Build failed.

First diagnostic:
{diagnostics[0]}

Location:
{diagnostics[0].Location.SourceTree?.GetRoot().FindNode(diagnostics[0].Location.SourceSpan)}

All diagnostics:
{string.Join(Environment.NewLine, diagnostics)}");
}

Assembly assembly;
using (var stream = new MemoryStream())
{
var result = compilation.Emit(stream);
if (!result.Success)
{
throw new InvalidOperationException(
$@"Build failed:
$@"Failed to emit compilation:
{string.Join(Environment.NewLine, result.Diagnostics)}");
}

Expand All @@ -107,12 +136,19 @@ private CSharpCompilationOptions CreateOptions()
generalDiagnosticOption: ReportDiagnostic.Error,
specificDiagnosticOptions: new Dictionary<string, ReportDiagnostic>
{
// Displays the text of a warning defined with the #warning directive
{ "CS1030", ReportDiagnostic.Suppress },

// Assuming assembly reference "Assembly Name #1" matches "Assembly Name #2", you may need to supply runtime policy
{ "CS1701", ReportDiagnostic.Suppress },
{ "CS1702", ReportDiagnostic.Suppress }, // Always thrown for .NET Core
{
"CS1705", ReportDiagnostic.Suppress
}, // Assembly 'AssemblyName1' uses 'TypeName' which has a higher version than referenced assembly 'AssemblyName2'
{ "CS8019", ReportDiagnostic.Suppress } // Unnecessary using directive.

// Assuming assembly reference "Assembly Name #1" matches "Assembly Name #2", you may need to supply runtime policy
{ "CS1702", ReportDiagnostic.Suppress },

// Assembly 'AssemblyName1' uses 'TypeName' which has a higher version than referenced assembly 'AssemblyName2'
{ "CS1705", ReportDiagnostic.Suppress },

// Unnecessary using directive.
{ "CS8019", ReportDiagnostic.Suppress }
});
}