Add an analyzer to prevent use of some internal shared source types (#6642)
* Initial implementation of AZC0020 * Updates * Update * refactor * whitespace * updates * Allow use of shared source types in Azure.Core * pr fb * pr fb * add back change to file missed in merge * Update tests * pr fb; + WIP for local variables * clean up * missed cleanup * missed file * Address warnings * Updates * refactor
This commit is contained in:
Родитель
49201efc7d
Коммит
76c21ffe3f
|
@ -0,0 +1,120 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
using Verifier = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier<Azure.ClientSdk.Analyzers.BannedTypesAnalyzer>;
|
||||||
|
|
||||||
|
namespace Azure.ClientSdk.Analyzers.Tests
|
||||||
|
{
|
||||||
|
public class AZC0020Tests
|
||||||
|
{
|
||||||
|
private List<(string fileName, string source)> _sharedSourceFiles;
|
||||||
|
|
||||||
|
public AZC0020Tests()
|
||||||
|
{
|
||||||
|
_sharedSourceFiles = new List<(string fileName, string source)>() {
|
||||||
|
|
||||||
|
("MutableJsonDocument.cs", @"
|
||||||
|
namespace Azure.Core.Json
|
||||||
|
{
|
||||||
|
internal sealed partial class MutableJsonDocument
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"),
|
||||||
|
|
||||||
|
("MutableJsonElement.cs", @"
|
||||||
|
namespace Azure.Core.Json
|
||||||
|
{
|
||||||
|
internal partial struct MutableJsonElement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AZC0020ProducedForMutableJsonDocumentUsage()
|
||||||
|
{
|
||||||
|
string code = @"
|
||||||
|
using System;
|
||||||
|
using Azure.Core.Json;
|
||||||
|
|
||||||
|
namespace LibraryNamespace
|
||||||
|
{
|
||||||
|
public class Model
|
||||||
|
{
|
||||||
|
private MutableJsonDocument {|AZC0020:_document|};
|
||||||
|
internal MutableJsonDocument {|AZC0020:Document|} => {|AZC0020:_document|};
|
||||||
|
internal event EventHandler<MutableJsonDocument> {|AZC0020:_docEvent|};
|
||||||
|
|
||||||
|
internal MutableJsonDocument {|AZC0020:GetDocument|}(MutableJsonDocument {|AZC0020:value|})
|
||||||
|
{
|
||||||
|
{|AZC0020:MutableJsonDocument mdoc = new MutableJsonDocument();|}
|
||||||
|
return mdoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AZC0020ProducedForMutableJsonElementUsage()
|
||||||
|
{
|
||||||
|
string code = @"
|
||||||
|
using Azure.Core.Json;
|
||||||
|
|
||||||
|
namespace LibraryNamespace
|
||||||
|
{
|
||||||
|
public class Model
|
||||||
|
{
|
||||||
|
private MutableJsonElement {|AZC0020:_element|};
|
||||||
|
internal MutableJsonElement {|AZC0020:Element|} => {|AZC0020:_element|};
|
||||||
|
|
||||||
|
internal MutableJsonElement {|AZC0020:GetDocument|}(MutableJsonElement {|AZC0020:value|})
|
||||||
|
{
|
||||||
|
{|AZC0020:MutableJsonElement element = new MutableJsonElement();|}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AZC0020NotProducedForAllowedTypeUsage()
|
||||||
|
{
|
||||||
|
string code = @"
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace LibraryNamespace
|
||||||
|
{
|
||||||
|
public class Model
|
||||||
|
{
|
||||||
|
JsonElement _element;
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AZC0020NotProducedForTypeWithBannedNameInAllowedNamespace()
|
||||||
|
{
|
||||||
|
string code = @"
|
||||||
|
namespace LibraryNamespace
|
||||||
|
{
|
||||||
|
public class MutableJsonDocument
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public class Model
|
||||||
|
{
|
||||||
|
MutableJsonDocument _document;
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
await Verifier.VerifyAnalyzerAsync(code, _sharedSourceFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -11,7 +12,7 @@ using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
using Microsoft.CodeAnalysis.Testing;
|
using Microsoft.CodeAnalysis.Testing;
|
||||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||||
|
|
||||||
namespace Azure.ClientSdk.Analyzers.Tests
|
namespace Azure.ClientSdk.Analyzers.Tests
|
||||||
{
|
{
|
||||||
public static class AzureAnalyzerVerifier<TAnalyzer> where TAnalyzer : DiagnosticAnalyzer, new()
|
public static class AzureAnalyzerVerifier<TAnalyzer> where TAnalyzer : DiagnosticAnalyzer, new()
|
||||||
{
|
{
|
||||||
|
@ -19,8 +20,8 @@ namespace Azure.ClientSdk.Analyzers.Tests
|
||||||
ReferenceAssemblies.Default.AddPackages(ImmutableArray.Create(
|
ReferenceAssemblies.Default.AddPackages(ImmutableArray.Create(
|
||||||
new PackageIdentity("Azure.Core", "1.26.0"),
|
new PackageIdentity("Azure.Core", "1.26.0"),
|
||||||
new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "1.1.0"),
|
new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "1.1.0"),
|
||||||
new PackageIdentity("System.Text.Json", "4.6.0"),
|
|
||||||
new PackageIdentity("Newtonsoft.Json", "12.0.3"),
|
new PackageIdentity("Newtonsoft.Json", "12.0.3"),
|
||||||
|
new PackageIdentity("System.Text.Json", "4.6.0"),
|
||||||
new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.3")));
|
new PackageIdentity("System.Threading.Tasks.Extensions", "4.5.3")));
|
||||||
|
|
||||||
public static CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> CreateAnalyzer(string source, LanguageVersion languageVersion = LanguageVersion.Latest)
|
public static CSharpAnalyzerTest<TAnalyzer, XUnitVerifier> CreateAnalyzer(string source, LanguageVersion languageVersion = LanguageVersion.Latest)
|
||||||
|
@ -37,7 +38,7 @@ namespace Azure.ClientSdk.Analyzers.Tests
|
||||||
TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck
|
TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion = LanguageVersion.Latest)
|
public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion = LanguageVersion.Latest)
|
||||||
=> CreateAnalyzer(source, languageVersion).RunAsync(CancellationToken.None);
|
=> CreateAnalyzer(source, languageVersion).RunAsync(CancellationToken.None);
|
||||||
|
|
||||||
public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] diagnostics)
|
public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] diagnostics)
|
||||||
|
@ -47,6 +48,16 @@ namespace Azure.ClientSdk.Analyzers.Tests
|
||||||
return test.RunAsync(CancellationToken.None);
|
return test.RunAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task VerifyAnalyzerAsync(string source, List<(string fileName, string source)> files)
|
||||||
|
{
|
||||||
|
var test = CreateAnalyzer(source);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
test.TestState.Sources.Add(file);
|
||||||
|
}
|
||||||
|
return test.RunAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
public static DiagnosticResult Diagnostic(string expectedDescriptor) => AnalyzerVerifier<TAnalyzer>.Diagnostic(expectedDescriptor);
|
public static DiagnosticResult Diagnostic(string expectedDescriptor) => AnalyzerVerifier<TAnalyzer>.Diagnostic(expectedDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace Azure.ClientSdk.Analyzers
|
||||||
|
{
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class BannedTypesAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
private static HashSet<string> BannedTypes = new HashSet<string>()
|
||||||
|
{
|
||||||
|
"Azure.Core.Json.MutableJsonDocument",
|
||||||
|
"Azure.Core.Json.MutableJsonElement",
|
||||||
|
"Azure.Core.Json.MutableJsonChange",
|
||||||
|
"Azure.Core.Json.MutableJsonChangeKind",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly string BannedTypesMessageArgs = string.Join(", ", BannedTypes);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptors.AZC0020);
|
||||||
|
|
||||||
|
public SymbolKind[] SymbolKinds { get; } = new[]
|
||||||
|
{
|
||||||
|
SymbolKind.Event,
|
||||||
|
SymbolKind.Field,
|
||||||
|
SymbolKind.Method,
|
||||||
|
SymbolKind.NamedType,
|
||||||
|
SymbolKind.Parameter,
|
||||||
|
SymbolKind.Property,
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.RegisterSymbolAction(c => Analyze(c), SymbolKinds);
|
||||||
|
context.RegisterSyntaxNodeAction(c => AnalyzeNode(c), SyntaxKind.LocalDeclarationStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Analyze(SymbolAnalysisContext context)
|
||||||
|
{
|
||||||
|
if (IsAzureCore(context.Symbol.ContainingAssembly))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (context.Symbol)
|
||||||
|
{
|
||||||
|
case IParameterSymbol parameterSymbol:
|
||||||
|
CheckType(parameterSymbol.Type, parameterSymbol, context.ReportDiagnostic);
|
||||||
|
break;
|
||||||
|
case IMethodSymbol methodSymbol:
|
||||||
|
CheckType(methodSymbol.ReturnType, methodSymbol, context.ReportDiagnostic);
|
||||||
|
break;
|
||||||
|
case IEventSymbol eventSymbol:
|
||||||
|
CheckType(eventSymbol.Type, eventSymbol, context.ReportDiagnostic);
|
||||||
|
break;
|
||||||
|
case IPropertySymbol propertySymbol:
|
||||||
|
CheckType(propertySymbol.Type, propertySymbol, context.ReportDiagnostic);
|
||||||
|
break;
|
||||||
|
case IFieldSymbol fieldSymbol:
|
||||||
|
CheckType(fieldSymbol.Type, fieldSymbol, context.ReportDiagnostic);
|
||||||
|
break;
|
||||||
|
case INamedTypeSymbol namedTypeSymbol:
|
||||||
|
CheckType(namedTypeSymbol.BaseType, namedTypeSymbol, context.ReportDiagnostic);
|
||||||
|
foreach (var iface in namedTypeSymbol.Interfaces)
|
||||||
|
{
|
||||||
|
CheckType(iface, namedTypeSymbol, context.ReportDiagnostic);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AnalyzeNode(SyntaxNodeAnalysisContext context)
|
||||||
|
{
|
||||||
|
if (IsAzureCore(context.ContainingSymbol.ContainingAssembly))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Node is LocalDeclarationStatementSyntax declaration)
|
||||||
|
{
|
||||||
|
ITypeSymbol type = context.SemanticModel.GetTypeInfo(declaration.Declaration.Type).Type;
|
||||||
|
|
||||||
|
CheckType(type, type, context.ReportDiagnostic, context.Node.GetLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Diagnostic CheckType(ITypeSymbol type, ISymbol symbol, Action<Diagnostic> reportDiagnostic, Location location = default)
|
||||||
|
{
|
||||||
|
if (type is INamedTypeSymbol namedTypeSymbol)
|
||||||
|
{
|
||||||
|
if (IsBannedType(namedTypeSymbol))
|
||||||
|
{
|
||||||
|
reportDiagnostic(Diagnostic.Create(Descriptors.AZC0020, location ?? symbol.Locations.First(), BannedTypesMessageArgs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namedTypeSymbol.IsGenericType)
|
||||||
|
{
|
||||||
|
foreach (var typeArgument in namedTypeSymbol.TypeArguments)
|
||||||
|
{
|
||||||
|
CheckType(typeArgument, symbol, reportDiagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAzureCore(IAssemblySymbol assembly)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
assembly.Name.Equals("Azure.Core") ||
|
||||||
|
assembly.Name.Equals("Azure.Core.Experimental");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsBannedType(INamedTypeSymbol namedTypeSymbol)
|
||||||
|
{
|
||||||
|
return BannedTypes.Contains($"{namedTypeSymbol.ContainingNamespace}.{namedTypeSymbol.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,9 +110,16 @@ namespace Azure.ClientSdk.Analyzers
|
||||||
"Invalid ServiceVersion member name.",
|
"Invalid ServiceVersion member name.",
|
||||||
"All parts of ServiceVersion members' names must begin with a number or uppercase letter and cannot have consecutive underscores.",
|
"All parts of ServiceVersion members' names must begin with a number or uppercase letter and cannot have consecutive underscores.",
|
||||||
"Usage",
|
"Usage",
|
||||||
|
DiagnosticSeverity.Warning, true);
|
||||||
|
|
||||||
|
public static DiagnosticDescriptor AZC0020 = new DiagnosticDescriptor(
|
||||||
|
nameof(AZC0020),
|
||||||
|
"Avoid using banned types in public APIs",
|
||||||
|
"The Azure.Core internal shared source types {0} should not be used outside of the Azure.Core library.",
|
||||||
|
"Usage",
|
||||||
DiagnosticSeverity.Warning, true);
|
DiagnosticSeverity.Warning, true);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region General
|
#region General
|
||||||
public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor(
|
public static DiagnosticDescriptor AZC0100 = new DiagnosticDescriptor(
|
||||||
nameof(AZC0100),
|
nameof(AZC0100),
|
||||||
|
|
Загрузка…
Ссылка в новой задаче