Add Client SDK analyzers (#21)
This commit is contained in:
Родитель
440db95ab1
Коммит
48bec817a9
|
@ -0,0 +1,78 @@
|
|||
# External variables:
|
||||
# ProjectFile - The project to build and test. This variable is defined in pipeline web ui because we want to be able to provide it at queue time and that isn't supported in yaml yet.
|
||||
# MaxParallelTestJobs - Maximum number of parallel test jobs
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
variables:
|
||||
DotNetCoreVersion: '2.1.503'
|
||||
|
||||
jobs:
|
||||
- job: 'Build'
|
||||
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
|
||||
steps:
|
||||
- task: DotNetCoreInstaller@0
|
||||
displayName: 'Use .NET Core sdk $(DotNetCoreVersion)'
|
||||
inputs:
|
||||
version: '$(DotNetCoreVersion)'
|
||||
|
||||
- script: 'dotnet pack $(ProjectFile) -o $(Build.ArtifactStagingDirectory) -warnaserror'
|
||||
displayName: 'Build and Package'
|
||||
env:
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_MULTILEVEL_LOOKUP: 0
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
condition: succeededOrFailed()
|
||||
displayName: 'Publish Artifacts'
|
||||
inputs:
|
||||
ArtifactName: packages
|
||||
|
||||
- job: 'Test'
|
||||
|
||||
strategy:
|
||||
maxParallel: $[ variables['MaxParallelTestJobs'] ]
|
||||
matrix:
|
||||
Linux:
|
||||
OSName: 'Linux'
|
||||
OSVmImage: 'ubuntu-16.04'
|
||||
Windows:
|
||||
OSName: 'Windows'
|
||||
OSVmImage: 'vs2017-win2016'
|
||||
MacOs:
|
||||
OSName: 'MacOS'
|
||||
OSVmImage: 'macOS-10.13'
|
||||
|
||||
pool:
|
||||
vmImage: '$(OSVmImage)'
|
||||
|
||||
steps:
|
||||
- task: DotNetCoreInstaller@0
|
||||
displayName: 'Use .NET Core sdk $(DotNetCoreVersion)'
|
||||
inputs:
|
||||
version: '$(DotNetCoreVersion)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build & Test'
|
||||
# condition: eq(variables['System.TeamProject'], 'public')
|
||||
env:
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_MULTILEVEL_LOOKUP: 0
|
||||
inputs:
|
||||
command: test
|
||||
projects: '$(ProjectFile)'
|
||||
publishTestResults: false
|
||||
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFiles: '**/*.trx'
|
||||
testRunTitle: '$(OSName) DotNet $(DotNetCoreVersion)'
|
||||
testResultsFormat: 'VSTest'
|
||||
mergeTestResults: true
|
|
@ -0,0 +1,5 @@
|
|||
<Project Sdk="Microsoft.Build.Traversal">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\**\*.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "1.0.45"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
public class AZC0001Tests
|
||||
{
|
||||
private readonly DiagnosticAnalyzerRunner _runner = new DiagnosticAnalyzerRunner(new ClientAssemblyNamespaceAnalyzer());
|
||||
|
||||
[Fact]
|
||||
public async Task AZC0001ProducedForInvalidNamespaces()
|
||||
{
|
||||
var testSource = TestSource.Read(@"
|
||||
namespace /*MM*/RandomNamespace
|
||||
{
|
||||
public class Program { }
|
||||
}
|
||||
");
|
||||
var diagnostics = await _runner.GetDiagnosticsAsync(testSource.Source);
|
||||
var diagnostic = Assert.Single(diagnostics);
|
||||
Assert.Equal("AZC0001", diagnostic.Id);
|
||||
Assert.Equal("Namespace 'RandomNamespace' shouldn't contain public types. Use one of the following pre-approved namespace groups:" +
|
||||
" Azure.Diagnostics, Azure.Cognitive, Azure.Iot, Azure.Networking, Azure.Runtime, Azure.Security, Azure.Storage", diagnostic.GetMessage());
|
||||
|
||||
AnalyzerAssert.DiagnosticLocation(testSource.DefaultMarkerLocation, diagnostic.Location);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AZC0001NotProducedForNamespacesWithPrivateMembersOnly()
|
||||
{
|
||||
var testSource = TestSource.Read(@"
|
||||
namespace RandomNamespace
|
||||
{
|
||||
internal class Program { }
|
||||
}
|
||||
");
|
||||
var diagnostics = await _runner.GetDiagnosticsAsync(testSource.Source);
|
||||
Assert.Empty(diagnostics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AZC0001NotProducedForAllowedNamespaces()
|
||||
{
|
||||
var testSource = TestSource.Read(@"
|
||||
namespace Azure.Storage.Hello
|
||||
{
|
||||
public class Program { }
|
||||
}
|
||||
");
|
||||
var diagnostics = await _runner.GetDiagnosticsAsync(testSource.Source);
|
||||
Assert.Empty(diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
public class AnalyzerAssert
|
||||
{
|
||||
public static void DiagnosticLocation(DiagnosticLocation expected, Location actual)
|
||||
{
|
||||
var actualSpan = actual.GetLineSpan();
|
||||
var actualLinePosition = actualSpan.StartLinePosition;
|
||||
|
||||
// Only check line position if there is an actual line in the real diagnostic
|
||||
if (actualLinePosition.Line > 0)
|
||||
{
|
||||
if (actualLinePosition.Line + 1 != expected.Line)
|
||||
{
|
||||
throw new DiagnosticLocationAssertException(
|
||||
expected,
|
||||
actual,
|
||||
$"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Only check column position if there is an actual column position in the real diagnostic
|
||||
if (actualLinePosition.Character > 0)
|
||||
{
|
||||
if (actualLinePosition.Character + 1 != expected.Column)
|
||||
{
|
||||
throw new DiagnosticLocationAssertException(
|
||||
expected,
|
||||
actual,
|
||||
$"Expected diagnostic to start at column \"{expected.Column}\" was actually on column \"{actualLinePosition.Character + 1}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DiagnosticLocationAssertException : EqualException
|
||||
{
|
||||
public DiagnosticLocationAssertException(
|
||||
DiagnosticLocation expected,
|
||||
Location actual,
|
||||
string message)
|
||||
: base(expected, actual)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public override string Message { get; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Azure.ClientSdk.Analyzers\Azure.ClientSdk.Analyzers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
|
||||
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="1.6.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
public class DiagnosticAnalyzerRunner
|
||||
{
|
||||
public DiagnosticAnalyzerRunner(DiagnosticAnalyzer analyzer)
|
||||
{
|
||||
Analyzer = analyzer;
|
||||
}
|
||||
|
||||
public DiagnosticAnalyzer Analyzer { get; }
|
||||
|
||||
public Task<Diagnostic[]> GetDiagnosticsAsync(string source)
|
||||
{
|
||||
var project = DiagnosticProject.Create(GetType().Assembly, new[] { source });
|
||||
return GetDiagnosticsAsync(new [] { project }, Analyzer);
|
||||
}
|
||||
|
||||
protected async Task<Diagnostic[]> GetDiagnosticsAsync(
|
||||
IEnumerable<Project> projects,
|
||||
DiagnosticAnalyzer analyzer)
|
||||
{
|
||||
var diagnostics = new List<Diagnostic>();
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var compilation = await project.GetCompilationAsync();
|
||||
|
||||
// Enable any additional diagnostics
|
||||
var options = ConfigureCompilationOptions(compilation.Options);
|
||||
var compilationWithAnalyzers = compilation
|
||||
.WithOptions(options)
|
||||
.WithAnalyzers(ImmutableArray.Create(analyzer));
|
||||
|
||||
var diags = await compilationWithAnalyzers.GetAllDiagnosticsAsync();
|
||||
|
||||
Assert.DoesNotContain(diags, d => d.Id == "AD0001");
|
||||
|
||||
// Filter out non-error diagnostics not produced by our analyzer
|
||||
// We want to KEEP errors because we might have written bad code. But sometimes we leave warnings in to make the
|
||||
// test code more convenient
|
||||
diags = diags.Where(d => d.Severity == DiagnosticSeverity.Error || analyzer.SupportedDiagnostics.Any(s => s.Id.Equals(d.Id))).ToImmutableArray();
|
||||
|
||||
foreach (var diag in diags)
|
||||
{
|
||||
if (diag.Location == Location.None || diag.Location.IsInMetadata)
|
||||
{
|
||||
diagnostics.Add(diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var document in projects.SelectMany(p => p.Documents))
|
||||
{
|
||||
var tree = await document.GetSyntaxTreeAsync();
|
||||
if (tree == diag.Location.SourceTree)
|
||||
{
|
||||
diagnostics.Add(diag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray();
|
||||
}
|
||||
|
||||
protected virtual CompilationOptions ConfigureCompilationOptions(CompilationOptions options)
|
||||
{
|
||||
return options.WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Location where the diagnostic appears, as determined by path, line number, and column number.
|
||||
/// </summary>
|
||||
public class DiagnosticLocation
|
||||
{
|
||||
public DiagnosticLocation(int line, int column)
|
||||
: this($"{DiagnosticProject.DefaultFilePathPrefix}.cs", line, column)
|
||||
{
|
||||
}
|
||||
|
||||
public DiagnosticLocation(string path, int line, int column)
|
||||
{
|
||||
if (line < -1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1");
|
||||
}
|
||||
|
||||
if (column < -1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1");
|
||||
}
|
||||
|
||||
Path = path;
|
||||
Line = line;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
public int Line { get; }
|
||||
public int Column { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Microsoft.Extensions.DependencyModel.Resolution;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
public class DiagnosticProject
|
||||
{
|
||||
/// <summary>
|
||||
/// File name prefix used to generate Documents instances from source.
|
||||
/// </summary>
|
||||
public static string DefaultFilePathPrefix = "Test";
|
||||
|
||||
/// <summary>
|
||||
/// Project name.
|
||||
/// </summary>
|
||||
public static string TestProjectName = "TestProject";
|
||||
|
||||
private static readonly Dictionary<Assembly, Solution> _solutionCache = new Dictionary<Assembly, Solution>();
|
||||
|
||||
public static Project Create(Assembly testAssembly, string[] sources)
|
||||
{
|
||||
Solution solution;
|
||||
lock (_solutionCache)
|
||||
{
|
||||
if (!_solutionCache.TryGetValue(testAssembly, out solution))
|
||||
{
|
||||
var projectId = ProjectId.CreateNewId(debugName: TestProjectName);
|
||||
solution = new AdhocWorkspace()
|
||||
.CurrentSolution
|
||||
.AddProject(projectId, TestProjectName, TestProjectName, LanguageNames.CSharp);
|
||||
|
||||
foreach (var defaultCompileLibrary in DependencyContext.Load(testAssembly).CompileLibraries)
|
||||
{
|
||||
foreach (var resolveReferencePath in defaultCompileLibrary.ResolveReferencePaths(new AppLocalResolver()))
|
||||
{
|
||||
solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(resolveReferencePath));
|
||||
}
|
||||
}
|
||||
|
||||
_solutionCache.Add(testAssembly, solution);
|
||||
}
|
||||
}
|
||||
|
||||
var testProject = solution.ProjectIds.Single();
|
||||
var fileNamePrefix = DefaultFilePathPrefix;
|
||||
|
||||
for (var i = 0; i < sources.Length; i++)
|
||||
{
|
||||
var newFileName = fileNamePrefix;
|
||||
if (sources.Length > 1)
|
||||
{
|
||||
newFileName += i;
|
||||
}
|
||||
newFileName += ".cs";
|
||||
|
||||
var documentId = DocumentId.CreateNewId(testProject, debugName: newFileName);
|
||||
solution = solution.AddDocument(documentId, newFileName, SourceText.From(sources[i]));
|
||||
}
|
||||
|
||||
return solution.GetProject(testProject);
|
||||
}
|
||||
|
||||
// Required to resolve compilation assemblies inside unit tests
|
||||
private class AppLocalResolver : ICompilationAssemblyResolver
|
||||
{
|
||||
public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> assemblies)
|
||||
{
|
||||
foreach (var assembly in library.Assemblies)
|
||||
{
|
||||
var dll = Path.Combine(Directory.GetCurrentDirectory(), "refs", Path.GetFileName(assembly));
|
||||
if (File.Exists(dll))
|
||||
{
|
||||
assemblies.Add(dll);
|
||||
return true;
|
||||
}
|
||||
|
||||
dll = Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(assembly));
|
||||
if (File.Exists(dll))
|
||||
{
|
||||
assemblies.Add(dll);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers.Tests
|
||||
{
|
||||
public class TestSource
|
||||
{
|
||||
private const string MarkerStart = "/*MM";
|
||||
private const string MarkerEnd = "*/";
|
||||
|
||||
public IDictionary<string, DiagnosticLocation> MarkerLocations { get; }
|
||||
= new Dictionary<string, DiagnosticLocation>(StringComparer.Ordinal);
|
||||
|
||||
public DiagnosticLocation DefaultMarkerLocation { get; private set; }
|
||||
|
||||
public string Source { get; private set; }
|
||||
|
||||
public static TestSource Read(string rawSource)
|
||||
{
|
||||
var testInput = new TestSource();
|
||||
var lines = rawSource.Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
var markerStartIndex = line.IndexOf(MarkerStart, StringComparison.Ordinal);
|
||||
if (markerStartIndex != -1)
|
||||
{
|
||||
var markerEndIndex = line.IndexOf(MarkerEnd, markerStartIndex, StringComparison.Ordinal);
|
||||
var markerName = line.Substring(markerStartIndex + 2, markerEndIndex - markerStartIndex - 2);
|
||||
var markerLocation = new DiagnosticLocation(i + 1, markerStartIndex + 1);
|
||||
if (testInput.DefaultMarkerLocation == null)
|
||||
{
|
||||
testInput.DefaultMarkerLocation = markerLocation;
|
||||
}
|
||||
|
||||
testInput.MarkerLocations.Add(markerName, markerLocation);
|
||||
line = line.Substring(0, markerStartIndex) + line.Substring(markerEndIndex + MarkerEnd.Length);
|
||||
}
|
||||
|
||||
lines[i] = line;
|
||||
}
|
||||
|
||||
testInput.Source = string.Join(Environment.NewLine, lines);
|
||||
return testInput;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
<BuildOutputTargetFolder>analyzers/dotnet/cs/</BuildOutputTargetFolder>
|
||||
<PackageVersion>0.1.0-preview1</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="2.8.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.8.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.0" PrivateAssets="All" />
|
||||
<PackageReference Update="NETStandard.Library" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class ClientAssemblyNamespaceAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
internal static readonly string[] AllowedNamespacePrefix = new[]
|
||||
{
|
||||
"Azure.Diagnostics",
|
||||
"Azure.Cognitive",
|
||||
"Azure.Iot",
|
||||
"Azure.Networking",
|
||||
"Azure.Runtime",
|
||||
"Azure.Security",
|
||||
"Azure.Storage"
|
||||
};
|
||||
|
||||
public ClientAssemblyNamespaceAnalyzer()
|
||||
{
|
||||
SupportedDiagnostics = ImmutableArray.Create(new[]
|
||||
{
|
||||
Descriptors.AZC0001
|
||||
});
|
||||
}
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterCompilationStartAction(
|
||||
analysisContext => {
|
||||
analysisContext.RegisterSymbolAction(symbolAnalysisContext => AnalyzeNamespace(symbolAnalysisContext), SymbolKind.Namespace);
|
||||
});
|
||||
}
|
||||
|
||||
private void AnalyzeNamespace(SymbolAnalysisContext symbolAnalysisContext)
|
||||
{
|
||||
var namespaceSymbol = (INamespaceSymbol)symbolAnalysisContext.Symbol;
|
||||
foreach (var member in namespaceSymbol.GetMembers())
|
||||
{
|
||||
if (member.IsType && member.DeclaredAccessibility == Accessibility.Public)
|
||||
{
|
||||
var displayString = namespaceSymbol.ToDisplayString();
|
||||
foreach (var prefix in AllowedNamespacePrefix)
|
||||
{
|
||||
if (displayString.StartsWith(prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var namespaceSymbolLocation in namespaceSymbol.Locations)
|
||||
{
|
||||
symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create(Descriptors.AZC0001, namespaceSymbolLocation, displayString));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Azure.ClientSdk.Analyzers
|
||||
{
|
||||
internal class Descriptors
|
||||
{
|
||||
private static readonly string AZC0001Title = "Use one of the following pre-approved namespace groups: " + string.Join(", ", ClientAssemblyNamespaceAnalyzer.AllowedNamespacePrefix);
|
||||
|
||||
public static DiagnosticDescriptor AZC0001 = new DiagnosticDescriptor(
|
||||
"AZC0001", AZC0001Title,
|
||||
"Namespace '{0}' shouldn't contain public types. " + AZC0001Title, "Usage", DiagnosticSeverity.Error, true);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче