This commit is contained in:
Pavel Krymets 2019-04-04 10:59:39 -07:00 коммит произвёл GitHub
Родитель 440db95ab1
Коммит 48bec817a9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 591 добавлений и 0 удалений

Просмотреть файл

@ -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

5
eng/dotnet.proj Normal file
Просмотреть файл

@ -0,0 +1,5 @@
<Project Sdk="Microsoft.Build.Traversal">
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\**\*.csproj" />
</ItemGroup>
</Project>

5
global.json Normal file
Просмотреть файл

@ -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);
}
}