提交 739b9a5e 编写于 作者: M manishv

User story 900556 (Workspace support for analyzers\rulesets): Add Workspace...

User story 900556 (Workspace support for analyzers\rulesets): Add Workspace APIs for Per-Project DiagnosticAnalyzers:
1) Property to get analyzers: Analyzers
2) Methods to update analyzers: AddAnalyzer(s), RemoveAnalyzer, WithAnalyzers
Also updated the IDE diagnostic service to now work for per-project analyzers.

Tests: I have added a test for new workspace APIs. I am working on adding tests for the diagnostic service per-project analyzer support, using the added workspace APIs for populating per-project analyzers. (changeset 1211161)
上级 ec8f50cb
......@@ -6,6 +6,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Collections.Immutable;
......@@ -154,6 +155,17 @@ public IReadOnlyList<ProjectReference> AllProjectReferences
}
}
/// <summary>
/// The list of all the diagnostic analyzers for this project.
/// </summary>
public IReadOnlyList<IDiagnosticAnalyzer> Analyzers
{
get
{
return this.projectState.Analyzers;
}
}
/// <summary>
/// The options used when building the compilation for this project.
/// </summary>
......@@ -446,6 +458,38 @@ public Project WithMetadataReferences(IEnumerable<MetadataReference> metadataRef
return this.Solution.WithProjectMetadataReferences(this.Id, metadataReferences).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to include the specified diagnostic analyzer.
/// </summary>
public Project AddAnalyzer(IDiagnosticAnalyzer analyzer)
{
return this.Solution.AddAnalyzer(this.Id, analyzer).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to include the specified diagnostic analyzers.
/// </summary>
public Project AddAnalyzers(IEnumerable<IDiagnosticAnalyzer> analyzers)
{
return this.Solution.AddAnalyzers(this.Id, analyzers).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to no longer include the specified diagnostic analyzer.
/// </summary>
public Project RemoveAnalyzer(IDiagnosticAnalyzer analyzer)
{
return this.Solution.RemoveAnalyzer(this.Id, analyzer).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to include the specified diagnostic analyzers.
/// </summary>
public Project WithAnalyzers(IEnumerable<IDiagnosticAnalyzer> analyzers)
{
return this.Solution.WithProjectAnalyzers(this.Id, analyzers).GetProject(this.Id);
}
/// <summary>
/// Creates a new document in a new instance of this project.
/// </summary>
......
......@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
......@@ -80,6 +81,30 @@ public IEnumerable<MetadataReference> GetRemovedMetadataReferences()
}
}
public IEnumerable<IDiagnosticAnalyzer> GetAddedAnalyzers()
{
var oldAnalyzers = new HashSet<IDiagnosticAnalyzer>(this.oldProject.Analyzers);
foreach (var analyzer in this.newProject.Analyzers)
{
if (!oldAnalyzers.Contains(analyzer))
{
yield return analyzer;
}
}
}
public IEnumerable<IDiagnosticAnalyzer> GetRemovedAnalyzers()
{
var newAnalyzers = new HashSet<IDiagnosticAnalyzer>(this.newProject.Analyzers);
foreach (var analyzer in this.oldProject.Analyzers)
{
if (!newAnalyzers.Contains(analyzer))
{
yield return analyzer;
}
}
}
public IEnumerable<DocumentId> GetAddedDocuments()
{
foreach (var id in this.newProject.DocumentIds)
......
......@@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......@@ -232,6 +233,12 @@ public ImmutableList<MetadataReference> MetadataReferences
get { return (ImmutableList<MetadataReference>)this.ProjectInfo.MetadataReferences; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public ImmutableList<IDiagnosticAnalyzer> Analyzers
{
get { return (ImmutableList<IDiagnosticAnalyzer>)this.ProjectInfo.Analyzers; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public ImmutableList<ProjectReference> ProjectReferences
{
......@@ -418,6 +425,41 @@ public ProjectState WithMetadataReferences(IEnumerable<MetadataReference> metada
projectInfo: this.ProjectInfo.WithMetadataReferences(metadataReferences.ToImmutableListOrEmpty()).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState AddAnalyzer(IDiagnosticAnalyzer analyzer)
{
Contract.Requires(!this.Analyzers.Contains(analyzer));
return this.With(
projectInfo: this.ProjectInfo.WithAnalyzers(this.Analyzers.Add(analyzer)).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState RemoveAnalyzer(IDiagnosticAnalyzer analyzer)
{
Contract.Requires(this.Analyzers.Contains(analyzer));
return this.With(
projectInfo: this.ProjectInfo.WithAnalyzers(this.Analyzers.Remove(analyzer)).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState AddAnalyzers(IEnumerable<IDiagnosticAnalyzer> analyzers)
{
var newAnalyzers = this.Analyzers;
foreach (var analyzer in analyzers)
{
Contract.Requires(!newAnalyzers.Contains(analyzer));
newAnalyzers = newAnalyzers.Add(analyzer);
}
return this.With(
projectInfo: this.ProjectInfo.WithAnalyzers(newAnalyzers).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState WithAnalyzers(IEnumerable<IDiagnosticAnalyzer> analyzers)
{
return this.With(
projectInfo: this.ProjectInfo.WithAnalyzers(analyzers.ToImmutableListOrEmpty()).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState AddDocument(DocumentState document)
{
Contract.Requires(!this.DocumentStates.ContainsKey(document.Id));
......
......@@ -9,6 +9,7 @@
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
......@@ -838,6 +839,16 @@ public Solution WithProjectReferences(ProjectId projectId, IEnumerable<ProjectRe
/// </summary>
public Solution AddMetadataReference(ProjectId projectId, MetadataReference metadataReference)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (metadataReference == null)
{
throw new ArgumentNullException("metadataReference");
}
CheckContainsProject(projectId);
return this.ForkProject(
......@@ -850,6 +861,16 @@ public Solution AddMetadataReference(ProjectId projectId, MetadataReference meta
/// </summary>
public Solution AddMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (metadataReferences == null)
{
throw new ArgumentNullException("metadataReferences");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).AddMetadataReferences(metadataReferences));
}
......@@ -860,6 +881,16 @@ public Solution AddMetadataReferences(ProjectId projectId, IEnumerable<MetadataR
/// </summary>
public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference metadataReference)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (metadataReference == null)
{
throw new ArgumentNullException("metadataReference");
}
CheckContainsProject(projectId);
return this.ForkProject(
......@@ -872,10 +903,100 @@ public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference m
/// </summary>
public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (metadataReferences == null)
{
throw new ArgumentNullException("metadataReferences");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).WithMetadataReferences(metadataReferences));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified diagnostic analyzer.
/// </summary>
public Solution AddAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (analyzer == null)
{
throw new ArgumentNullException("analyzer");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).AddAnalyzer(analyzer));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified diagnostic analyzers.
/// </summary>
public Solution AddAnalyzers(ProjectId projectId, IEnumerable<IDiagnosticAnalyzer> analyzers)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (analyzers == null)
{
throw new ArgumentNullException("analyzers");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).AddAnalyzers(analyzers));
}
/// <summary>
/// Create a new solution instance with the project specified updated to no longer include
/// the specified diagnostic analyzer.
/// </summary>
public Solution RemoveAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (analyzer == null)
{
throw new ArgumentNullException("analyzer");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).RemoveAnalyzer(analyzer));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include only the
/// specified diagnostic analyzers.
/// </summary>
public Solution WithProjectAnalyzers(ProjectId projectId, IEnumerable<IDiagnosticAnalyzer> analyzers)
{
if (projectId == null)
{
throw new ArgumentNullException("projectId");
}
if (analyzers == null)
{
throw new ArgumentNullException("analyzers");
}
CheckContainsProject(projectId);
return this.ForkProject(this.GetProjectState(projectId).WithAnalyzers(analyzers));
}
[SuppressMessage("Microsoft.StyleCop.CSharp.SpacingRules", "SA1008:OpeningParenthesisMustBeSpacedCorrectly", Justification = "Working around StyleCop bug 7080")]
private Solution AddDocument(DocumentState state)
{
......
......@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
......@@ -537,6 +538,40 @@ protected internal void OnMetadataReferenceRemoved(ProjectId projectId, Metadata
}
}
/// <summary>
/// Call this method when a diagnostic analyzer is added to a project in the host environment.
/// </summary>
protected internal void OnAnalyzerAdded(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
using (this.serializationLock.DisposableWait())
{
CheckProjectIsInCurrentSolution(projectId);
CheckProjectDoesNotHaveAnalyzer(projectId, analyzer);
var oldSolution = this.CurrentSolution;
var newSolution = this.SetCurrentSolution(oldSolution.AddAnalyzer(projectId, analyzer));
this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, oldSolution, newSolution, projectId);
}
}
/// <summary>
/// Call this method when a diagnostic analyzer is removed from a project in the host environment.
/// </summary>
protected internal void OnAnalyzerRemoved(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
using (this.serializationLock.DisposableWait())
{
CheckProjectIsInCurrentSolution(projectId);
CheckProjectHasAnalyzer(projectId, analyzer);
var oldSolution = this.CurrentSolution;
var newSolution = this.SetCurrentSolution(oldSolution.RemoveAnalyzer(projectId, analyzer));
this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, oldSolution, newSolution, projectId);
}
}
/// <summary>
/// Call this method when a document is added to a project in the host environment.
/// </summary>
......@@ -764,6 +799,18 @@ protected virtual void ApplyProjectChanges(ProjectChanges projectChanges)
this.AddMetadataReference(projectChanges.ProjectId, metadata);
}
// removed analyzers
foreach (var analyzer in projectChanges.GetRemovedAnalyzers())
{
this.RemoveAnalyzer(projectChanges.ProjectId, analyzer);
}
// added analyzers
foreach (var analyzer in projectChanges.GetAddedAnalyzers())
{
this.AddAnalyzer(projectChanges.ProjectId, analyzer);
}
// removed documents
foreach (var documentId in projectChanges.GetRemovedDocuments())
{
......@@ -858,6 +905,26 @@ protected virtual void RemoveMetadataReference(ProjectId projectId, MetadataRefe
throw new NotSupportedException();
}
/// <summary>
/// This method is called during ApplyChanges to add a diagnostic analyzer to a project.
///
/// Override this method to implement the capability of adding diagnostic analyzers.
/// </summary>
protected virtual void AddAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
throw new NotSupportedException();
}
/// <summary>
/// This method is called during ApplyChanges to remove a diagnostic analyzer from a project.
///
/// Override this method to implement the capability of removing diagnostic analyzers.
/// </summary>
protected virtual void RemoveAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
throw new NotSupportedException();
}
/// <summary>
/// This method is called during ApplyChanges to add a new document to a project.
///
......@@ -1000,6 +1067,28 @@ protected void CheckProjectDoesNotHaveMetadataReference(ProjectId projectId, Met
}
}
/// <summary>
/// Throws an exception if a project does not have a specific diagnostic analyzer.
/// </summary>
protected void CheckProjectHasAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
if (!this.CurrentSolution.GetProject(projectId).Analyzers.Contains(analyzer))
{
throw new ArgumentException(string.Format(WorkspacesResources.AnalyzerIsNotPresent, analyzer));
}
}
/// <summary>
/// Throws an exception if a project already has a specific diagnostic analyzer.
/// </summary>
protected void CheckProjectDoesNotHaveAnalyzer(ProjectId projectId, IDiagnosticAnalyzer analyzer)
{
if (this.CurrentSolution.GetProject(projectId).Analyzers.Contains(analyzer))
{
throw new ArgumentException(string.Format(WorkspacesResources.AnalyzerIsAlreadyPresent, analyzer));
}
}
/// <summary>
/// Throws an exception if a document is not part of the current solution.
/// </summary>
......
......@@ -69,6 +69,24 @@ internal class WorkspacesResources {
}
}
/// <summary>
/// Looks up a localized string similar to {0} is already present..
/// </summary>
internal static string AnalyzerIsAlreadyPresent {
get {
return ResourceManager.GetString("AnalyzerIsAlreadyPresent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} is not present..
/// </summary>
internal static string AnalyzerIsNotPresent {
get {
return ResourceManager.GetString("AnalyzerIsNotPresent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Arrays with more than one dimension cannot be serialized..
/// </summary>
......
......@@ -191,6 +191,12 @@
<data name="MetadataIsAlreadyReferenced" xml:space="preserve">
<value>Metadata is already referenced.</value>
</data>
<data name="AnalyzerIsNotPresent" xml:space="preserve">
<value>{0} is not present.</value>
</data>
<data name="AnalyzerIsAlreadyPresent" xml:space="preserve">
<value>{0} is already present.</value>
</data>
<data name="DocumentVersionIsDifferent" xml:space="preserve">
<value>The specified document is not a version of this document.</value>
</data>
......
// Copyright (c) Microsoft Open Technologies, Inc. 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.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
......@@ -11,6 +12,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
......@@ -19,6 +21,7 @@
using Microsoft.CodeAnalysis.WorkspaceServices;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.CodeAnalysis.UnitTests
{
......@@ -338,6 +341,76 @@ where symbol.Name.Equals("CSharpClass")
ValidateSolutionAndCompilations(solution);
}
private class MockDiagnosticAnalyzer : IDiagnosticAnalyzer
{
public ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
throw new NotImplementedException();
}
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestProjectDiagnosticAnalyzers()
{
var solution = CreateSolution();
var project1 = ProjectId.CreateNewId();
solution = solution.AddProject(project1, "foo", "foo.dll", LanguageNames.CSharp);
Assert.Empty(solution.Projects.Single().Analyzers);
var analyzer = new MockDiagnosticAnalyzer();
// Test AddAnalyzer
var newSolution = solution.AddAnalyzer(project1, analyzer);
var actualAnalyzers = newSolution.Projects.Single().Analyzers;
Assert.Equal(1, actualAnalyzers.Count);
Assert.Equal(analyzer, actualAnalyzers[0]);
// Cannot add the existing analyzer
Assert.Throws<TraceAssertException>(() => newSolution.AddAnalyzer(project1, analyzer));
// Test ProjectChanges
var changes = newSolution.GetChanges(solution).GetProjectChanges().Single();
var addedAnalyzer = changes.GetAddedAnalyzers().Single();
Assert.Equal(analyzer, addedAnalyzer);
var removedAnalyzers = changes.GetRemovedAnalyzers();
Assert.Empty(removedAnalyzers);
solution = newSolution;
// Test RemoveAnalyzer
solution = solution.RemoveAnalyzer(project1, analyzer);
actualAnalyzers = solution.Projects.Single().Analyzers;
Assert.Empty(actualAnalyzers);
// Cannot remove non-existing analyzer
Assert.Throws<TraceAssertException>(() => solution.RemoveAnalyzer(project1, analyzer));
// Test AddAnalyzers
var secondAnalyzer = new MockDiagnosticAnalyzer();
var analyzers = ImmutableArray.Create(analyzer, secondAnalyzer);
solution = solution.AddAnalyzers(project1, analyzers);
actualAnalyzers = solution.Projects.Single().Analyzers;
Assert.Equal(2, actualAnalyzers.Count);
Assert.Equal(analyzer, actualAnalyzers[0]);
Assert.Equal(secondAnalyzer, actualAnalyzers[1]);
// Cannot add the existing analyzers
Assert.Throws<TraceAssertException>(() => solution.AddAnalyzers(project1, analyzers));
solution = solution.RemoveAnalyzer(project1, analyzer);
actualAnalyzers = solution.Projects.Single().Analyzers;
Assert.Equal(1, actualAnalyzers.Count);
Assert.Equal(secondAnalyzer, actualAnalyzers[0]);
// Test WithAnalyzers
solution = solution.WithProjectAnalyzers(project1, analyzers);
actualAnalyzers = solution.Projects.Single().Analyzers;
Assert.Equal(2, actualAnalyzers.Count);
Assert.Equal(analyzer, actualAnalyzers[0]);
Assert.Equal(secondAnalyzer, actualAnalyzers[1]);
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestProjectCompilationOptions()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册