diff --git a/benchmark/EFCore.Benchmarks/EFCore.Benchmarks.csproj b/benchmark/EFCore.Benchmarks/EFCore.Benchmarks.csproj index 85cabbef025..57aeca4b90b 100644 --- a/benchmark/EFCore.Benchmarks/EFCore.Benchmarks.csproj +++ b/benchmark/EFCore.Benchmarks/EFCore.Benchmarks.csproj @@ -12,7 +12,7 @@ - + diff --git a/eng/Versions.props b/eng/Versions.props index df73e37d4ac..3ce1995e5f7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -28,4 +28,7 @@ 5.0.0-rc.1.20409.3 5.0.0-rc.1.20409.3 + + 3.7.0 + diff --git a/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md b/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000000..433bac626f2 --- /dev/null +++ b/src/EFCore.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,18 @@ +## Release 2.1.0 + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +EF1000 | Usage | Warning | RawSqlStringInjectionDiagnosticAnalyzer, [Documentation](https://docs.microsoft.com/ef/core/querying/raw-sql) + +## Release 3.0.0 + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +EF1001 | Usage | Warning | InternalUsageDiagnosticAnalyzer + +### Removed Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +EF1000 | Security | Disabled | RawSqlStringInjectionDiagnosticAnalyzer, [Documentation](https://docs.microsoft.com/ef/core/querying/raw-sql) \ No newline at end of file diff --git a/src/EFCore.Analyzers/AnalyzerReleases.Unshipped.md b/src/EFCore.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/EFCore.Analyzers/EFCore.Analyzers.csproj b/src/EFCore.Analyzers/EFCore.Analyzers.csproj index 893e3c2475f..d1f0c17289d 100644 --- a/src/EFCore.Analyzers/EFCore.Analyzers.csproj +++ b/src/EFCore.Analyzers/EFCore.Analyzers.csproj @@ -15,7 +15,8 @@ - + + @@ -32,4 +33,9 @@ + + + + + diff --git a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs index eb4dd635156..d63c4e5ca0b 100644 --- a/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs +++ b/src/EFCore.Analyzers/InternalUsageDiagnosticAnalyzer.cs @@ -5,21 +5,23 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +using CSharpSyntax = Microsoft.CodeAnalysis.CSharp.Syntax; +using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace Microsoft.EntityFrameworkCore { - [DiagnosticAnalyzer(LanguageNames.CSharp)] + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public class InternalUsageDiagnosticAnalyzer : DiagnosticAnalyzer { public const string Id = "EF1001"; public const string MessageFormat - = "{0} is an internal API that supports the Entity Framework Core infrastructure and " + - "not subject to the same compatibility standards as public APIs. " + - "It may be changed or removed without notice in any release."; + = "{0} is an internal API that supports the Entity Framework Core infrastructure and " + + "not subject to the same compatibility standards as public APIs. " + + "It may be changed or removed without notice in any release."; protected const string DefaultTitle = "Internal EF Core API usage."; protected const string Category = "Usage"; @@ -40,84 +42,282 @@ private static readonly DiagnosticDescriptor _descriptor public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction( - AnalyzeNode, - SyntaxKind.SimpleMemberAccessExpression, - SyntaxKind.ObjectCreationExpression, - SyntaxKind.ClassDeclaration); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterOperationAction(AnalyzeNode, + OperationKind.FieldReference, + OperationKind.PropertyReference, + OperationKind.MethodReference, + OperationKind.EventReference, + OperationKind.Invocation, + OperationKind.ObjectCreation, + OperationKind.VariableDeclaration, + OperationKind.TypeOf); + + context.RegisterSymbolAction(AnalyzeSymbol, + SymbolKind.NamedType, + SymbolKind.Method, + SymbolKind.Property, + SymbolKind.Field, + SymbolKind.Event); + } + + private static void AnalyzeNode(OperationAnalysisContext context) + { + switch (context.Operation.Kind) + { + case OperationKind.FieldReference: + AnalyzeMember(context, ((IFieldReferenceOperation)context.Operation).Field); + break; + case OperationKind.PropertyReference: + AnalyzeMember(context, ((IPropertyReferenceOperation)context.Operation).Property); + break; + case OperationKind.EventReference: + AnalyzeMember(context, ((IEventReferenceOperation)context.Operation).Event); + break; + case OperationKind.MethodReference: + AnalyzeMember(context, ((IMethodReferenceOperation)context.Operation).Method); + break; + case OperationKind.ObjectCreation: + AnalyzeMember(context, ((IObjectCreationOperation)context.Operation).Constructor); + break; + case OperationKind.Invocation: + AnalyzeInvocation(context, (IInvocationOperation)context.Operation); + break; + case OperationKind.VariableDeclaration: + AnalyzeVariableDeclaration(context, ((IVariableDeclarationOperation)context.Operation)); + break; + case OperationKind.TypeOf: + AnalyzeTypeof(context, ((ITypeOfOperation)context.Operation)); + break; + default: + throw new ArgumentException($"Unexpected {nameof(OperationKind)}: {context.Operation.Kind}"); + } + + } + + private static void AnalyzeMember(OperationAnalysisContext context, ISymbol symbol) + { + if ((object)symbol.ContainingAssembly == context.Compilation.Assembly) + { + // Skip all methods inside the same assembly - internal access is fine + return; + } + + var containingType = symbol.ContainingType; + + if (HasInternalAttribute(symbol)) + { + ReportDiagnostic(context, symbol.Name == ".ctor" ? (object)containingType : $"{containingType}.{symbol.Name}"); + return; + } + + if (IsInternal(context, containingType)) + { + ReportDiagnostic(context, containingType); + } } - private void AnalyzeNode(SyntaxNodeAnalysisContext context) + private static void AnalyzeInvocation(OperationAnalysisContext context, IInvocationOperation invocation) { - switch (context.Node) + // First check for any internal type parameters + foreach (var a in invocation.TargetMethod.TypeArguments) { - case MemberAccessExpressionSyntax memberAccessSyntax: + if (IsInternal(context, a)) { - if (context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol is ISymbol symbol - && !Equals(symbol.ContainingAssembly, context.Compilation.Assembly)) - { - var containingType = symbol.ContainingType; - - if (HasInternalAttribute(symbol)) - { - context.ReportDiagnostic( - Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), $"{containingType}.{symbol.Name}")); - return; - } - - if (IsInInternalNamespace(containingType) - || HasInternalAttribute(containingType)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, memberAccessSyntax.Name.GetLocation(), containingType)); - return; - } - } + context.ReportDiagnostic(Diagnostic.Create(_descriptor, context.Operation.Syntax.GetLocation(), a)); + } + } + + // Then check the method being invoked + AnalyzeMember(context, invocation.TargetMethod); + } + private static void AnalyzeVariableDeclaration(OperationAnalysisContext context, IVariableDeclarationOperation variableDeclaration) + { + foreach (var declarator in variableDeclaration.Declarators) + { + if (IsInternal(context, declarator.Symbol.Type)) + { + var syntax = context.Operation.Syntax switch + { + CSharpSyntax.VariableDeclarationSyntax s => s.Type, + _ => context.Operation.Syntax + }; + context.ReportDiagnostic(Diagnostic.Create(_descriptor, syntax.GetLocation(), declarator.Symbol.Type)); return; } + } + } + + private static void AnalyzeTypeof(OperationAnalysisContext context, ITypeOfOperation typeOf) + { + if (IsInternal(context, typeOf.TypeOperand)) + { + ReportDiagnostic(context, typeOf.TypeOperand); + } + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + switch (context.Symbol) + { + case INamedTypeSymbol symbol: + AnalyzeNamedTypeSymbol(context, symbol); + break; + + case IMethodSymbol symbol: + AnalyzeMethodTypeSymbol(context, symbol); + break; + + case IFieldSymbol symbol: + AnalyzeMemberDeclarationTypeSymbol(context, symbol, symbol.Type); + break; + + case IPropertySymbol symbol: + AnalyzeMemberDeclarationTypeSymbol(context, symbol, symbol.Type); + break; + + case IEventSymbol symbol: + AnalyzeMemberDeclarationTypeSymbol(context, symbol, symbol.Type); + break; + + default: + throw new ArgumentException($"Unexpected {nameof(ISymbol)}: {context.Symbol.GetType().Name}"); + } + } + + private static void AnalyzeNamedTypeSymbol(SymbolAnalysisContext context, INamedTypeSymbol symbol) + { + if (symbol.BaseType is ITypeSymbol baseSymbol + && IsInternal(context, baseSymbol)) + { + foreach (var declaringSyntax in symbol.DeclaringSyntaxReferences) + { + var location = declaringSyntax.GetSyntax() switch + { + CSharpSyntax.ClassDeclarationSyntax s when s.BaseList?.Types.Count > 0 + => s.BaseList.Types[0].GetLocation(), + { } otherSyntax => otherSyntax.GetLocation() + }; + + context.ReportDiagnostic(Diagnostic.Create(_descriptor, location, baseSymbol)); + } + } - case ObjectCreationExpressionSyntax creationSyntax: + foreach (var iface in symbol.Interfaces.Where(i => IsInternal(context, i))) + { + foreach (var declaringSyntax in symbol.DeclaringSyntaxReferences) { - if (context.SemanticModel.GetSymbolInfo(context.Node, context.CancellationToken).Symbol is ISymbol symbol - && !Equals(symbol.ContainingAssembly, context.Compilation.Assembly)) + var location = declaringSyntax.GetSyntax() switch { - var containingType = symbol.ContainingType; - - if (HasInternalAttribute(symbol)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, creationSyntax.GetLocation(), containingType)); - return; - } - - if (IsInInternalNamespace(containingType) - || HasInternalAttribute(containingType)) - { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, creationSyntax.Type.GetLocation(), containingType)); - return; - } - } + CSharpSyntax.ClassDeclarationSyntax s => s.Identifier.GetLocation(), + { } otherSyntax => otherSyntax.GetLocation() + }; - return; + context.ReportDiagnostic(Diagnostic.Create(_descriptor, location, iface)); } + } + } - case ClassDeclarationSyntax declarationSyntax: + private static void AnalyzeMethodTypeSymbol(SymbolAnalysisContext context, IMethodSymbol symbol) + { + if (symbol.MethodKind == MethodKind.PropertyGet + || symbol.MethodKind == MethodKind.PropertySet) + { + // Property getters/setters are handled via IPropertySymbol + return; + } + if (IsInternal(context, symbol.ReturnType)) + { + foreach (var declaringSyntax in symbol.DeclaringSyntaxReferences) { - if (context.SemanticModel.GetDeclaredSymbol(declarationSyntax)?.BaseType is ISymbol symbol - && !Equals(symbol.ContainingAssembly, context.Compilation.Assembly) - && (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol)) - && declarationSyntax.BaseList?.Types.Count > 0) + var location = declaringSyntax.GetSyntax() switch { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, declarationSyntax.BaseList.Types[0].GetLocation(), symbol)); - } + CSharpSyntax.MethodDeclarationSyntax s => s.ReturnType.GetLocation(), + { } otherSyntax => otherSyntax.GetLocation() + }; - return; + context.ReportDiagnostic(Diagnostic.Create(_descriptor, location, symbol.ReturnType)); + } + } + + foreach (var paramSymbol in symbol.Parameters.Where(ps => IsInternal(context, ps.Type))) + { + foreach (var declaringSyntax in paramSymbol.DeclaringSyntaxReferences) + { + var location = declaringSyntax.GetSyntax() switch + { + CSharpSyntax.ParameterSyntax s when s.Type != null => s.Type.GetLocation(), + { } otherSyntax => otherSyntax.GetLocation() + }; + + context.ReportDiagnostic(Diagnostic.Create(_descriptor, location, paramSymbol.Type)); } } } + private static void AnalyzeMemberDeclarationTypeSymbol( + SymbolAnalysisContext context, + ISymbol declarationSymbol, + ITypeSymbol typeSymbol) + { + if (IsInternal(context, typeSymbol)) + { + foreach (var declaringSyntax in declarationSymbol.DeclaringSyntaxReferences) + { + ReportDiagnostic(context, declaringSyntax.GetSyntax(), typeSymbol); + } + } + } + + private static void ReportDiagnostic(OperationAnalysisContext context, object messageArg) + => context.ReportDiagnostic(Diagnostic.Create(_descriptor, NarrowDownSyntax(context.Operation.Syntax).GetLocation(), messageArg)); + + private static void ReportDiagnostic(SymbolAnalysisContext context, SyntaxNode syntax, object messageArg) + => context.ReportDiagnostic(Diagnostic.Create(_descriptor, NarrowDownSyntax(syntax).GetLocation(), messageArg)); + + /// + /// Given a syntax node, pattern matches some known types and returns a narrowed-down node for the type syntax which + /// should be reported in diagnostics. + /// + private static SyntaxNode NarrowDownSyntax(SyntaxNode syntax) + => syntax switch + { + CSharpSyntax.InvocationExpressionSyntax s + when s.Expression is CSharpSyntax.MemberAccessExpressionSyntax memberAccessSyntax + => memberAccessSyntax.Name, + CSharpSyntax.MemberAccessExpressionSyntax s => s.Name, + CSharpSyntax.ObjectCreationExpressionSyntax s => s.Type, + CSharpSyntax.PropertyDeclarationSyntax s => s.Type, + CSharpSyntax.VariableDeclaratorSyntax declarator + => declarator.Parent is CSharpSyntax.VariableDeclarationSyntax declaration + ? declaration.Type + : (SyntaxNode)declarator, + CSharpSyntax.TypeOfExpressionSyntax s => s.Type, + + VBSyntax.InvocationExpressionSyntax s + when s.Expression is VBSyntax.MemberAccessExpressionSyntax memberAccessSyntax + => memberAccessSyntax.Name, + VBSyntax.MemberAccessExpressionSyntax s => s.Name, + VBSyntax.ObjectCreationExpressionSyntax s => s.Type, + VBSyntax.TypeOfExpressionSyntax s => s.Type, + + _ => syntax + }; + + private static bool IsInternal(SymbolAnalysisContext context, ITypeSymbol symbol) + => (object)symbol.ContainingAssembly != context.Compilation.Assembly + && (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol)); + + private static bool IsInternal(OperationAnalysisContext context, ITypeSymbol symbol) + => (object)symbol.ContainingAssembly != context.Compilation.Assembly + && (IsInInternalNamespace(symbol) || HasInternalAttribute(symbol)); + private static bool HasInternalAttribute(ISymbol symbol) - => symbol != null && symbol.GetAttributes().Any(a => a.AttributeClass.Name == "EntityFrameworkInternalAttribute"); + => symbol != null + && symbol.GetAttributes().Any(a => + a.AttributeClass.ToDisplayString() == "Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkInternalAttribute"); private static bool IsInInternalNamespace(ISymbol symbol) { diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs index c0d36d32986..f430be2d4c0 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs @@ -365,9 +365,12 @@ private void AddInclude( var includeMethod = navigation.IsCollection ? _includeCollectionMethodInfo : _includeReferenceMethodInfo; var includingClrType = navigation.DeclaringEntityType.ClrType; var relatedEntityClrType = navigation.TargetEntityType.ClrType; +#pragma warning disable EF1001 // Internal EF Core API usage. var entityEntryVariable = _trackQueryResults ? shaperBlock.Variables.Single(v => v.Type == typeof(InternalEntityEntry)) : (Expression)Expression.Constant(null, typeof(InternalEntityEntry)); +#pragma warning restore EF1001 // Internal EF Core API usage. + var concreteEntityTypeVariable = shaperBlock.Variables.Single(v => v.Type == typeof(IEntityType)); var inverseNavigation = navigation.Inverse; var fixup = GenerateFixup( @@ -394,7 +397,9 @@ private static readonly MethodInfo _includeReferenceMethodInfo .GetDeclaredMethod(nameof(IncludeReference)); private static void IncludeReference( +#pragma warning disable EF1001 // Internal EF Core API usage. InternalEntityEntry entry, +#pragma warning restore EF1001 // Internal EF Core API usage. object entity, IEntityType entityType, TIncludedEntity relatedEntity, @@ -437,7 +442,9 @@ private static readonly MethodInfo _includeCollectionMethodInfo .GetDeclaredMethod(nameof(IncludeCollection)); private static void IncludeCollection( +#pragma warning disable EF1001 // Internal EF Core API usage. InternalEntityEntry entry, +#pragma warning restore EF1001 // Internal EF Core API usage. object entity, IEntityType entityType, IEnumerable relatedEntities, diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs index 0327bd298f2..1ec832b0e78 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.ReadItemQueryingEnumerable.cs @@ -259,10 +259,10 @@ private bool TryGenerateIdFromKeys(IProperty idProperty, out object value) { var entityEntry = Activator.CreateInstance(_readItemExpression.EntityType.ClrType); -#pragma warning disable EF1001 +#pragma warning disable EF1001 // Internal EF Core API usage. var internalEntityEntry = new InternalEntityEntryFactory().Create( _cosmosQueryContext.Context.GetDependencies().StateManager, _readItemExpression.EntityType, entityEntry); -#pragma warning restore EF1001 +#pragma warning restore EF1001 // Internal EF Core API usage. foreach (var keyProperty in _readItemExpression.EntityType.FindPrimaryKey().Properties) { @@ -270,17 +270,17 @@ private bool TryGenerateIdFromKeys(IProperty idProperty, out object value) if (TryGetParameterValue(property, out var parameterValue)) { +#pragma warning disable EF1001 // Internal EF Core API usage. internalEntityEntry[property] = parameterValue; +#pragma warning restore EF1001 // Internal EF Core API usage. } } #pragma warning disable EF1001 // Internal EF Core API usage. internalEntityEntry.SetEntityState(EntityState.Added); -#pragma warning restore EF1001 // Internal EF Core API usage. value = internalEntityEntry[idProperty]; -#pragma warning disable EF1001 // Internal EF Core API usage. internalEntityEntry.SetEntityState(EntityState.Detached); #pragma warning restore EF1001 // Internal EF Core API usage. diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index e0e1fea226b..d3bf4cc1526 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -82,13 +82,17 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer public MigrationsModelDiffer( [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] IMigrationsAnnotationProvider migrationsAnnotations, +#pragma warning disable EF1001 // Internal EF Core API usage. [NotNull] IChangeDetector changeDetector, +#pragma warning restore EF1001 // Internal EF Core API usage. [NotNull] IUpdateAdapterFactory updateAdapterFactory, [NotNull] CommandBatchPreparerDependencies commandBatchPreparerDependencies) { Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(migrationsAnnotations, nameof(migrationsAnnotations)); +#pragma warning disable EF1001 // Internal EF Core API usage. Check.NotNull(changeDetector, nameof(changeDetector)); +#pragma warning restore EF1001 // Internal EF Core API usage. Check.NotNull(updateAdapterFactory, nameof(updateAdapterFactory)); Check.NotNull(commandBatchPreparerDependencies, nameof(commandBatchPreparerDependencies)); @@ -137,7 +141,9 @@ public MigrationsModelDiffer( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// +#pragma warning disable EF1001 // Internal EF Core API usage. protected virtual IChangeDetector ChangeDetector { get; } +#pragma warning restore EF1001 // Internal EF Core API usage. /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs index c13fbcbb96b..0f7d4f6ddde 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerAnnotationProvider.cs @@ -31,7 +31,9 @@ public class SqlServerAnnotationProvider : RelationalAnnotationProvider /// Initializes a new instance of this class. /// /// Parameter object containing dependencies for this service. +#pragma warning disable EF1001 // Internal EF Core API usage. public SqlServerAnnotationProvider([NotNull] RelationalAnnotationProviderDependencies dependencies) +#pragma warning restore EF1001 // Internal EF Core API usage. : base(dependencies) { } diff --git a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs b/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs index a6b5cc80599..76a0b5888a1 100644 --- a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs +++ b/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsAnnotationProvider.cs @@ -31,7 +31,9 @@ public class SqlServerMigrationsAnnotationProvider : MigrationsAnnotationProvide /// Initializes a new instance of this class. /// /// Parameter object containing dependencies for this service. +#pragma warning disable EF1001 // Internal EF Core API usage. public SqlServerMigrationsAnnotationProvider([NotNull] MigrationsAnnotationProviderDependencies dependencies) +#pragma warning restore EF1001 // Internal EF Core API usage. : base(dependencies) { } diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index 6050b86eb9a..959090f53e0 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -33,7 +33,9 @@ public class SqliteAnnotationProvider : RelationalAnnotationProvider /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// +#pragma warning disable EF1001 // Internal EF Core API usage. public SqliteAnnotationProvider([NotNull] RelationalAnnotationProviderDependencies dependencies) +#pragma warning restore EF1001 // Internal EF Core API usage. : base(dependencies) { } diff --git a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Test.csproj b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Test.csproj index 563ffcb2141..20045b2a73e 100644 --- a/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Test.csproj +++ b/test/EFCore.Analyzers.Tests/EFCore.Analyzers.Test.csproj @@ -11,7 +11,8 @@ - + + diff --git a/test/EFCore.Analyzers.Tests/InternalUsageDiagnosticAnalyzerTest.cs b/test/EFCore.Analyzers.Tests/InternalUsageDiagnosticAnalyzerTest.cs index f7b4acc1950..fe15fac6eac 100644 --- a/test/EFCore.Analyzers.Tests/InternalUsageDiagnosticAnalyzerTest.cs +++ b/test/EFCore.Analyzers.Tests/InternalUsageDiagnosticAnalyzerTest.cs @@ -15,74 +15,161 @@ public class InternalUsageDiagnosticAnalyzerTest : DiagnosticAnalyzerTestBase protected override DiagnosticAnalyzer CreateDiagnosticAnalyzer() => new InternalUsageDiagnosticAnalyzer(); [ConditionalFact] - public async Task No_warning_on_ef_non_internal() - => await AssertNoDiagnostics( - @" -var a = new Microsoft.EntityFrameworkCore.Infrastructure.Annotatable(); -var x = a.GetAnnotations(); -"); + public Task Invocation_on_type_in_internal_namespace() + => Test( + "var x = typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes).DisplayName();", + "Microsoft.EntityFrameworkCore.Internal.MethodInfoExtensions", + "DisplayName"); - #region Namespace + [ConditionalFact] + public Task Instantiation_on_type_in_internal_namespace() + => Test( + "new CoreSingletonOptions();", + "Microsoft.EntityFrameworkCore.Internal.CoreSingletonOptions", + "CoreSingletonOptions"); [ConditionalFact] - public async Task Warning_on_ef_internal_namespace_invocation() + public async Task Base_type() { - var (diagnostics, source) = await GetDiagnosticsAsync( - @"var x = typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes).DisplayName();"); - var diagnostic = diagnostics.Single(); + var source = @" +class MyClass : Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter { + MyClass() : base(null, null) {} +}"; - Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); - Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); - Assert.Equal( - string.Format(InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Internal.MethodInfoExtensions"), - diagnostic.GetMessage()); + var diagnostics = await GetDiagnosticsFullSourceAsync(source); - var span = diagnostic.Location.SourceSpan; - Assert.Equal("DisplayName", source[span.Start..span.End]); + Assert.Collection(diagnostics, + diagnostic => + { + Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); + Assert.Equal( + string.Format( + InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter"), + diagnostic.GetMessage()); + + var span = diagnostic.Location.SourceSpan; + Assert.Equal( + "Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter", + source[span.Start..span.End]); + }, + diagnostic => + { + Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); + Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); + Assert.Equal( + string.Format( + InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter"), + diagnostic.GetMessage()); + + var span = diagnostic.Location.SourceSpan; + Assert.Equal(": base(null, null)", source[span.Start..span.End]); + }); } [ConditionalFact] - public async Task Warning_on_ef_internal_namespace_instantiation() - { - var (diagnostics, source) = await GetDiagnosticsAsync(@"new CoreSingletonOptions();"); - var diagnostic = diagnostics.Single(); + public Task Implemented_interface() + => TestFullSource( + @" +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; - Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); - Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); - Assert.Equal( - string.Format(InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Internal.CoreSingletonOptions"), - diagnostic.GetMessage()); +class MyClass : IDbSetSource { + public object Create(DbContext context, Type type) => null; + public object Create(DbContext context, string name, Type type) => null; +}", + "Microsoft.EntityFrameworkCore.Internal.IDbSetSource", + "MyClass"); - var span = diagnostic.Location.SourceSpan; - Assert.Equal("CoreSingletonOptions", source[span.Start..span.End]); - } + [ConditionalFact] + public Task Access_property_with_internal_attribute() + => Test( + "var x = Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkRelationalServicesBuilder.RelationalServices.Count;", + "Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkRelationalServicesBuilder.RelationalServices", + "RelationalServices"); [ConditionalFact] - public async Task Warning_on_ef_internal_namespace_subclass() - { - var source = @" -class MyClass : Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter { - MyClass() : base (null, null) {} -}"; + public Task Instantiation_with_ctor_with_internal_attribute() + => Test( + "new Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies(null, null);", + "Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies", + "Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies"); - var diagnostics = await GetDiagnosticsFullSourceAsync(source); - var diagnostic = diagnostics.Single(); + [ConditionalFact] + public Task Local_variable_declaration() + => Test( + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager state = null;", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); - Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); - Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); - Assert.Equal( - string.Format( - InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter"), - diagnostic.GetMessage()); + [ConditionalFact] + public Task Generic_type_parameter_in_method_call() + => Test( + @" +void SomeGenericMethod() {} - var span = diagnostic.Location.SourceSpan; - Assert.Equal( - "Microsoft.EntityFrameworkCore.Storage.Internal.RawRelationalParameter", - source[span.Start..span.End]); - } +SomeGenericMethod();", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "SomeGenericMethod()"); + + [ConditionalFact] + public Task Typeof() + => Test( + "var t = typeof(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager);", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); + + [ConditionalFact] + public Task Field_declaration() + => TestFullSource( + @" +class MyClass { + private readonly Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager StateManager; +}", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); + + [ConditionalFact] + public Task Property_declaration() + => TestFullSource( + @" +class MyClass { + private Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager StateManager { get; set; } +}", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); + + [ConditionalFact] + public Task Method_declaration_return_type() + => TestFullSource( + @" +class MyClass { + private Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager Foo() => null; +}", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); [ConditionalFact] - public async Task No_warning_on_ef_internal_namespace_in_same_assembly() + public Task Method_declaration_parameter() + => TestFullSource( + @" +class MyClass { + private void Foo(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager stateManager) {} +}", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager", + "Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IStateManager"); + + [ConditionalFact] + public async Task No_warning_on_non_internal() + => await AssertNoDiagnostics( + @" +var a = new Microsoft.EntityFrameworkCore.Infrastructure.Annotatable(); +var x = a.GetAnnotations(); +"); + + [ConditionalFact] + public async Task No_warning_in_same_assembly() { var diagnostics = await GetDiagnosticsFullSourceAsync( @" @@ -107,51 +194,42 @@ public void Main(string[] args) { Assert.Empty(diagnostics); } - #endregion Namespace - - #region Attribute - - [ConditionalFact] - public async Task Warning_on_ef_internal_attribute_property_access() + private async Task Test( + string source, + string expectedInternalApi, + string expectedDiagnosticSpan) { - var (diagnostics, source) = await GetDiagnosticsAsync( - @"var x = Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkRelationalServicesBuilder.RelationalServices.Count;"); - //throw new Exception("FOO: " + string.Join(", ", diagnostics.Select(d => d.ToString()))); - var diagnostic = diagnostics.Single(); + var (diagnostics, fullSource) = await GetDiagnosticsAsync(source); + var diagnostic = Assert.Single(diagnostics); Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.Equal( - string.Format( - InternalUsageDiagnosticAnalyzer.MessageFormat, - "Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkRelationalServicesBuilder.RelationalServices"), + string.Format(InternalUsageDiagnosticAnalyzer.MessageFormat, expectedInternalApi), diagnostic.GetMessage()); var span = diagnostic.Location.SourceSpan; - Assert.Equal("RelationalServices", source[span.Start..span.End]); + Assert.Equal(expectedDiagnosticSpan, fullSource[span.Start..span.End]); } - [ConditionalFact] - public async Task Warning_on_ef_internal_name_instantiation() + private async Task TestFullSource( + string fullSource, + string expectedInternalApi, + string expectedDiagnosticSpan) { - var (diagnostics, source) = - await GetDiagnosticsAsync(@"new Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies(null, null);"); - var diagnostic = diagnostics.Single(); + var diagnostics = await GetDiagnosticsFullSourceAsync(fullSource); + var diagnostic = Assert.Single(diagnostics); Assert.Equal(InternalUsageDiagnosticAnalyzer.Id, diagnostic.Id); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.Equal( - string.Format( - InternalUsageDiagnosticAnalyzer.MessageFormat, "Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies"), + string.Format(InternalUsageDiagnosticAnalyzer.MessageFormat, expectedInternalApi), diagnostic.GetMessage()); var span = diagnostic.Location.SourceSpan; - Assert.Equal( - "new Microsoft.EntityFrameworkCore.Update.UpdateSqlGeneratorDependencies(null, null)", source[span.Start..span.End]); + Assert.Equal(expectedDiagnosticSpan, fullSource[span.Start..span.End]); } - #endregion Attribute - protected override Task<(Diagnostic[], string)> GetDiagnosticsAsync(string source, params string[] extraUsings) => base.GetDiagnosticsAsync(source, extraUsings.Concat(new[] { "Microsoft.EntityFrameworkCore.Internal" }).ToArray()); } diff --git a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj index b25cbbd3a15..78604fd4692 100644 --- a/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj +++ b/test/EFCore.Design.Tests/EFCore.Design.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/test/ef.Tests/ef.Tests.csproj b/test/ef.Tests/ef.Tests.csproj index 7ae6986d93c..85631f36ce4 100644 --- a/test/ef.Tests/ef.Tests.csproj +++ b/test/ef.Tests/ef.Tests.csproj @@ -18,7 +18,7 @@ - +