Skip to content

Commit

Permalink
Merge pull request #329 from sharwell/reference-assemblies
Browse files Browse the repository at this point in the history
Support reference assemblies
  • Loading branch information
sharwell authored Nov 4, 2019
2 parents 180edd6 + 1cbea12 commit 801f721
Show file tree
Hide file tree
Showing 11 changed files with 1,449 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ public string TestCode
/// </summary>
public List<string> DisabledDiagnostics { get; } = new List<string>();

public ReferenceAssemblies ReferenceAssemblies { get; set; } = ReferenceAssemblies.Default;

/// <summary>
/// Gets a collection of transformation functions to apply to <see cref="Workspace.Options"/> during diagnostic
/// or code fix test setup.
Expand Down Expand Up @@ -484,9 +486,9 @@ private static bool IsInSourceFile(DiagnosticResult result, (string filename, So
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A collection of <see cref="Diagnostic"/>s that surfaced in the source code, sorted by
/// <see cref="Diagnostic.Location"/>.</returns>
private Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, ImmutableArray<DiagnosticAnalyzer> analyzers, IVerifier verifier, CancellationToken cancellationToken)
private async Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, ImmutableArray<DiagnosticAnalyzer> analyzers, IVerifier verifier, CancellationToken cancellationToken)
{
return GetSortedDiagnosticsAsync(GetSolution(sources, additionalFiles, additionalProjects, additionalMetadataReferences, verifier), analyzers, CompilerDiagnostics, cancellationToken);
return await GetSortedDiagnosticsAsync(await GetSolutionAsync(sources, additionalFiles, additionalProjects, additionalMetadataReferences, verifier, cancellationToken), analyzers, CompilerDiagnostics, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -558,12 +560,13 @@ protected virtual AnalyzerOptions GetAnalyzerOptions(Project project)
/// <param name="additionalProjects">Additional projects to include in the solution.</param>
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="verifier">The verifier to use for test assertions.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A solution containing a project with the specified sources and additional files.</returns>
private Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, IVerifier verifier)
private async Task<Solution> GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, IVerifier verifier, CancellationToken cancellationToken)
{
verifier.LanguageIsSupported(Language);

var project = CreateProject(sources, additionalFiles, additionalProjects, additionalMetadataReferences, Language);
var project = await CreateProjectAsync(sources, additionalFiles, additionalProjects, additionalMetadataReferences, Language, cancellationToken);
var documents = project.Documents.ToArray();

verifier.Equal(sources.Length, documents.Length, "Amount of sources did not match amount of Documents created");
Expand All @@ -575,7 +578,7 @@ private Solution GetSolution((string filename, SourceText content)[] sources, (s
/// Create a project using the input strings as sources.
/// </summary>
/// <remarks>
/// <para>This method first creates a <see cref="Project"/> by calling <see cref="CreateProjectImpl"/>, and then
/// <para>This method first creates a <see cref="Project"/> by calling <see cref="CreateProjectImplAsync"/>, and then
/// applies compilation options to the project by calling <see cref="ApplyCompilationOptions"/>.</para>
/// </remarks>
/// <param name="sources">Classes in the form of strings.</param>
Expand All @@ -584,11 +587,12 @@ private Solution GetSolution((string filename, SourceText content)[] sources, (s
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
protected async Task<Project> CreateProjectAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language, CancellationToken cancellationToken)
{
var project = CreateProjectImpl(sources, additionalFiles, additionalProjects, additionalMetadataReferences, language);
var project = await CreateProjectImplAsync(sources, additionalFiles, additionalProjects, additionalMetadataReferences, language, cancellationToken);
return ApplyCompilationOptions(project);
}

Expand All @@ -601,15 +605,16 @@ protected Project CreateProject((string filename, SourceText content)[] sources,
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
protected virtual async Task<Project> CreateProjectImplAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language, CancellationToken cancellationToken)
{
var fileNamePrefix = DefaultFilePathPrefix;
var fileExt = DefaultFileExt;

var projectId = ProjectId.CreateNewId(debugName: DefaultTestProjectName);
var solution = CreateSolution(projectId, language);
var solution = await CreateSolutionAsync(projectId, language, cancellationToken);

foreach (var projectState in additionalProjects)
{
Expand Down Expand Up @@ -655,8 +660,9 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content
/// </summary>
/// <param name="projectId">The project identifier to use.</param>
/// <param name="language">The language for which the solution is being created.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>The created solution.</returns>
protected virtual Solution CreateSolution(ProjectId projectId, string language)
protected virtual async Task<Solution> CreateSolutionAsync(ProjectId projectId, string language, CancellationToken cancellationToken)
{
var compilationOptions = CreateCompilationOptions();

Expand All @@ -666,38 +672,17 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language)
xmlReferenceResolver.XmlReferences.Add(xmlReference.Key, xmlReference.Value);
}

compilationOptions = compilationOptions.WithXmlReferenceResolver(xmlReferenceResolver);
compilationOptions = compilationOptions
.WithXmlReferenceResolver(xmlReferenceResolver)
.WithAssemblyIdentityComparer(ReferenceAssemblies.AssemblyIdentityComparer);

var solution = CreateWorkspace()
.CurrentSolution
.AddProject(projectId, DefaultTestProjectName, DefaultTestProjectName, language)
.WithProjectCompilationOptions(projectId, compilationOptions)
.AddMetadataReference(projectId, MetadataReferences.CorlibReference)
.AddMetadataReference(projectId, MetadataReferences.SystemReference)
.AddMetadataReference(projectId, MetadataReferences.SystemCoreReference)
.AddMetadataReference(projectId, MetadataReferences.CodeAnalysisReference)
.AddMetadataReference(projectId, MetadataReferences.SystemCollectionsImmutableReference);

if (language == LanguageNames.VisualBasic)
{
solution = solution.AddMetadataReference(projectId, MetadataReferences.MicrosoftVisualBasicReference);
}
.WithProjectCompilationOptions(projectId, compilationOptions);

if (MetadataReferences.MscorlibFacadeReference != null)
{
solution = solution.AddMetadataReference(projectId, MetadataReferences.MscorlibFacadeReference);
}

if (MetadataReferences.SystemRuntimeReference != null)
{
solution = solution.AddMetadataReference(projectId, MetadataReferences.SystemRuntimeReference);
}

if (typeof(object).GetTypeInfo().Assembly.GetType("System.ValueTuple`2", throwOnError: false) == null
&& MetadataReferences.SystemValueTupleReference != null)
{
solution = solution.AddMetadataReference(projectId, MetadataReferences.SystemValueTupleReference);
}
var metadataReferences = await ReferenceAssemblies.ResolveAsync(language, cancellationToken);
solution = solution.AddMetadataReferences(projectId, metadataReferences);

foreach (var transform in OptionsTransforms)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void Add(string path)

private static MetadataReference GetOrCreateReference(string path)
{
return s_referencesFromFiles.GetOrAdd(path, p => MetadataReference.CreateFromFile(p));
return s_referencesFromFiles.GetOrAdd(path, p => MetadataReferences.CreateReferenceFromFile(p));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,95 +1,46 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;

#if NETSTANDARD1_5
using System.IO;
#endif

namespace Microsoft.CodeAnalysis.Testing
{
/// <summary>
/// Metadata references used to create test projects.
/// </summary>
public static class MetadataReferences
{
public static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib"));
public static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).GetTypeInfo().Assembly.Location).WithAliases(ImmutableArray.Create("global", "system"));
public static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location);
public static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).GetTypeInfo().Assembly.Location);
public static readonly MetadataReference SystemCollectionsImmutableReference = MetadataReference.CreateFromFile(typeof(ImmutableArray).GetTypeInfo().Assembly.Location);

internal static readonly MetadataReference? MscorlibFacadeReference;
internal static readonly MetadataReference? SystemRuntimeReference;
internal static readonly MetadataReference? SystemValueTupleReference;

private static MetadataReference? _microsoftVisualBasicReference;
private static readonly Func<string, DocumentationProvider?> s_createDocumentationProvider;

static MetadataReferences()
{
#if NETSTANDARD1_5
if (typeof(string).GetTypeInfo().Assembly.ExportedTypes.Any(x => x.Name == "System.ValueTuple"))
{
// mscorlib contains ValueTuple, so no need to add a separate reference
MscorlibFacadeReference = null;
SystemRuntimeReference = null;
SystemValueTupleReference = null;
}
else
{
MscorlibFacadeReference = MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(Lazy<,>).GetTypeInfo().Assembly.Location), "mscorlib.dll"));
SystemRuntimeReference = MetadataReference.CreateFromFile(typeof(Lazy<,>).GetTypeInfo().Assembly.Location);
SystemValueTupleReference = MetadataReference.CreateFromFile(typeof(ValueTuple<,>).GetTypeInfo().Assembly.Location);
}
#elif NETSTANDARD2_0
// mscorlib contains ValueTuple, so no need to add a separate reference
MscorlibFacadeReference = null;
SystemRuntimeReference = null;
SystemValueTupleReference = null;
#elif NET452
// System.Object is already in mscorlib
MscorlibFacadeReference = null;
Func<string, DocumentationProvider?> createDocumentationProvider = _ => null;

var systemRuntime = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(x => x.GetName().Name == "System.Runtime");
if (systemRuntime != null)
var xmlDocumentationProvider = typeof(Workspace).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.XmlDocumentationProvider");
if (xmlDocumentationProvider is object)
{
SystemRuntimeReference = MetadataReference.CreateFromFile(systemRuntime.Location);
var createFromFile = xmlDocumentationProvider.GetTypeInfo().GetMethod("CreateFromFile", new[] { typeof(string) });
if (createFromFile is object)
{
var xmlDocCommentFilePath = Expression.Parameter(typeof(string), "xmlDocCommentFilePath");
var body = Expression.Convert(
Expression.Call(createFromFile, xmlDocCommentFilePath),
typeof(DocumentationProvider));
var expression = Expression.Lambda<Func<string, DocumentationProvider>>(body, xmlDocCommentFilePath);
createDocumentationProvider = expression.Compile();
}
}

var systemValueTuple = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(x => x.GetName().Name == "System.ValueTuple");
if (systemValueTuple != null)
{
SystemValueTupleReference = MetadataReference.CreateFromFile(systemValueTuple.Location);
}
#else
#error Unsupported target framework.
#endif
s_createDocumentationProvider = createDocumentationProvider;
}

public static MetadataReference MicrosoftVisualBasicReference
internal static MetadataReference CreateReferenceFromFile(string path)
{
[MethodImpl(MethodImplOptions.NoInlining)]
get
{
if (_microsoftVisualBasicReference is null)
{
try
{
_microsoftVisualBasicReference = MetadataReference.CreateFromFile(typeof(Microsoft.VisualBasic.Strings).GetTypeInfo().Assembly.Location);
}
catch (Exception e)
{
throw new PlatformNotSupportedException("Microsoft.VisualBasic is not supported on this platform", e);
}
}

return _microsoftVisualBasicReference;
}
var documentationFile = Path.ChangeExtension(path, ".xml");
return MetadataReference.CreateFromFile(path, documentation: s_createDocumentationProvider(documentationFile));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.5;netstandard2.0;net452</TargetFrameworks>
<TargetFrameworks>netstandard1.5;netstandard2.0;net452;net46;net472</TargetFrameworks>
<AssemblyName>Microsoft.CodeAnalysis.Analyzer.Testing</AssemblyName>
<RootNamespace>Microsoft.CodeAnalysis.Testing</RootNamespace>
</PropertyGroup>
Expand All @@ -13,12 +13,42 @@
<PackageTags>Roslyn Analyzer Test Framework Common</PackageTags>
</PropertyGroup>

<Choose>
<When Condition="'$(TargetFramework)' == 'net452'">
<PropertyGroup>
<NuGetApiVersion>4.5.3</NuGetApiVersion>
</PropertyGroup>
</When>
<When Condition="'$(TargetFramework)' == 'net46'">
<PropertyGroup>
<NuGetApiVersion>4.9.4</NuGetApiVersion>
</PropertyGroup>
</When>
<When Condition="'$(TargetFramework)' == 'netstandard1.5'">
<PropertyGroup>
<NuGetApiVersion>4.6.4</NuGetApiVersion>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<NuGetApiVersion>5.3.1</NuGetApiVersion>
</PropertyGroup>
</Otherwise>
</Choose>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.6.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="1.0.1" />
<PackageReference Include="Microsoft.VisualBasic" Version="10.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Composition" Version="15.6.36" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

<!-- Use PrivateAssets=compile to avoid exposing our NuGet dependencies downstream as public API. -->
<PackageReference Include="NuGet.Common" Version="$(NuGetApiVersion)" PrivateAssets="compile" />
<PackageReference Include="NuGet.Protocol" Version="$(NuGetApiVersion)" PrivateAssets="compile" />
<PackageReference Include="NuGet.Resolver" Version="$(NuGetApiVersion)" PrivateAssets="compile" />
<PackageReference Include="NuGet.Packaging" Version="$(NuGetApiVersion)" />

<!-- Use PrivateAssets=compile to avoid exposing our DiffPlex dependency downstream as public API. -->
<PackageReference Include="DiffPlex" Version="1.4.4" PrivateAssets="compile" />
</ItemGroup>
Expand Down
Loading

0 comments on commit 801f721

Please sign in to comment.