Skip to content

Commit

Permalink
Merge pull request #328 from sharwell/project-references
Browse files Browse the repository at this point in the history
Initial support for project references
  • Loading branch information
sharwell authored May 28, 2019
2 parents d95a98a + 406ef98 commit 2bc3564
Show file tree
Hide file tree
Showing 25 changed files with 185 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public virtual async Task RunAsync(CancellationToken cancellationToken = default
var fixableDiagnostics = ImmutableArray<string>.Empty;
var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath);

await VerifyDiagnosticsAsync(testState.Sources.ToArray(), testState.AdditionalFiles.ToArray(), testState.AdditionalReferences.ToArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false);
await VerifyDiagnosticsAsync(testState.Sources.ToArray(), testState.AdditionalFiles.ToArray(), testState.AdditionalProjects.ToArray(), testState.AdditionalReferences.ToArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -182,21 +182,22 @@ protected internal virtual DiagnosticDescriptor GetDefaultDiagnostic(DiagnosticA
/// </summary>
/// <param name="sources">An array of strings to create source documents from to run the analyzers on.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <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="expected">A collection of <see cref="DiagnosticResult"/>s that should appear after the analyzer
/// is run on the sources.</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 <see cref="Task"/> representing the asynchronous operation.</returns>
protected async Task VerifyDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
protected async Task VerifyDiagnosticsAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
var analyzers = GetDiagnosticAnalyzers().ToImmutableArray();
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources, additionalFiles, additionalMetadataReferences, analyzers, verifier, cancellationToken).ConfigureAwait(false), analyzers, expected, verifier);
await VerifyGeneratedCodeDiagnosticsAsync(analyzers, sources, additionalFiles, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
await VerifySuppressionDiagnosticsAsync(analyzers, sources, additionalFiles, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources, additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, verifier, cancellationToken).ConfigureAwait(false), analyzers, expected, verifier);
await VerifyGeneratedCodeDiagnosticsAsync(analyzers, sources, additionalFiles, additionalProjects, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
await VerifySuppressionDiagnosticsAsync(analyzers, sources, additionalFiles, additionalProjects, additionalMetadataReferences, expected, verifier, cancellationToken).ConfigureAwait(false);
}

private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
if (TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedCodeCheck))
{
Expand All @@ -218,10 +219,10 @@ private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray<Diagnostic

var generatedCodeVerifier = verifier.PushContext("Verifying exclusions in <auto-generated> code");
var commentPrefix = Language == LanguageNames.CSharp ? "//" : "'";
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $" {commentPrefix} <auto-generated>\r\n"))).ToArray(), additionalFiles, additionalMetadataReferences, analyzers, generatedCodeVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, generatedCodeVerifier);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $" {commentPrefix} <auto-generated>\r\n"))).ToArray(), additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, generatedCodeVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, generatedCodeVerifier);
}

private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken)
{
if (TestBehaviors.HasFlag(TestBehaviors.SkipSuppressionCheck))
{
Expand All @@ -245,7 +246,7 @@ private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray<DiagnosticAn
var suppressionVerifier = verifier.PushContext($"Verifying exclusions in '{prefix}' code");
var suppressedDiagnostics = expected.Where(x => IsSubjectToExclusion(x, sources)).Select(x => x.Id).Distinct();
var suppression = prefix + " " + string.Join(", ", suppressedDiagnostics);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToArray(), additionalFiles, additionalMetadataReferences, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier);
VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToArray(), additionalFiles, additionalProjects, additionalMetadataReferences, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier);
}

/// <summary>
Expand Down Expand Up @@ -476,15 +477,16 @@ private static bool IsInSourceFile(DiagnosticResult result, (string filename, So
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <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="analyzers">The analyzers to be run on the sources.</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 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, MetadataReference[] additionalMetadataReferences, ImmutableArray<DiagnosticAnalyzer> analyzers, IVerifier verifier, CancellationToken cancellationToken)
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)
{
return GetSortedDiagnosticsAsync(GetSolution(sources, additionalFiles, additionalMetadataReferences, verifier), analyzers, CompilerDiagnostics, cancellationToken);
return GetSortedDiagnosticsAsync(GetSolution(sources, additionalFiles, additionalProjects, additionalMetadataReferences, verifier), analyzers, CompilerDiagnostics, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -553,14 +555,15 @@ protected virtual AnalyzerOptions GetAnalyzerOptions(Project project)
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <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>
/// <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, MetadataReference[] additionalMetadataReferences, IVerifier verifier)
private Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, IVerifier verifier)
{
verifier.LanguageIsSupported(Language);

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

verifier.Equal(sources.Length, documents.Length, "Amount of sources did not match amount of Documents created");
Expand All @@ -577,15 +580,16 @@ private Solution GetSolution((string filename, SourceText content)[] sources, (s
/// </remarks>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <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="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</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, MetadataReference[] additionalMetadataReferences, string language)
protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
{
language = language ?? language;
var project = CreateProjectImpl(sources, additionalFiles, additionalMetadataReferences, language);
var project = CreateProjectImpl(sources, additionalFiles, additionalProjects, additionalMetadataReferences, language);
return ApplyCompilationOptions(project);
}

Expand All @@ -594,19 +598,35 @@ protected Project CreateProject((string filename, SourceText content)[] sources,
/// </summary>
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="additionalFiles">Additional documents to include in the project.</param>
/// <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="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</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, MetadataReference[] additionalMetadataReferences, string language)
protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, ProjectState[] additionalProjects, MetadataReference[] additionalMetadataReferences, string language)
{
var fileNamePrefix = DefaultFilePathPrefix;
var fileExt = DefaultFileExt;

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

foreach (var projectState in additionalProjects)
{
var additionalProjectId = ProjectId.CreateNewId(debugName: projectState.Name);
solution = solution.AddProject(additionalProjectId, projectState.Name, projectState.AssemblyName, projectState.Language);

for (var i = 0; i < projectState.Sources.Count; i++)
{
(var newFileName, var source) = sources[i];
var documentId = DocumentId.CreateNewId(additionalProjectId, debugName: newFileName);
solution = solution.AddDocument(documentId, newFileName, source);
}

solution = solution.AddProjectReference(projectId, new ProjectReference(additionalProjectId));
}

solution = solution.AddMetadataReferences(projectId, additionalMetadataReferences);

for (var i = 0; i < sources.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.CodeAnalysis.Testing
{
public abstract class ProjectState
{
private readonly string _defaultPrefix;
private readonly string _defaultExtension;

protected ProjectState(string name, string defaultPrefix, string defaultExtension)
{
Name = name;
_defaultPrefix = defaultPrefix;
_defaultExtension = defaultExtension;

Sources = new SourceFileList(defaultPrefix, defaultExtension);
}

public string Name { get; }

public string AssemblyName => Name;

public abstract string Language { get; }

/// <summary>
/// Gets the set of source files for analyzer or code fix testing. Files may be added to this list using one of
/// the <see cref="SourceFileList.Add(string)"/> methods.
/// </summary>
public SourceFileList Sources { get; }

public MetadataReferenceCollection AdditionalReferences { get; } = new MetadataReferenceCollection();
}
}
Loading

0 comments on commit 2bc3564

Please sign in to comment.