提交 62851d6f 编写于 作者: H Heejae Chang

Merge pull request #9017 from heejaechang/askmode

Disable diagnostic service for projects that we know we don't have all information to provide good results.
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
{
public class DiagnosticAnalyzerServiceTests
{
[Fact]
public async Task TestHasSuccessfullyLoadedBeingFalse()
{
var workspace = new AdhocWorkspace();
var document = GetDocumentFromIncompleteProject(workspace);
// create listener/service/analyzer
var listener = new AsynchronousOperationListener();
var service = new MyDiagnosticAnalyzerService(new Analyzer(), listener);
var analyzer = service.CreateIncrementalAnalyzer(workspace);
// listen to events
// check empty since this could be called to clear up existing diagnostics
service.DiagnosticsUpdated += (s, a) => Assert.Empty(a.Diagnostics);
// now call each analyze method. none of them should run.
await analyzer.AnalyzeSyntaxAsync(document, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, cancellationToken: CancellationToken.None).ConfigureAwait(false);
// wait for all events to raised
await listener.CreateWaitTask().ConfigureAwait(false);
}
[Fact]
public async Task TestHasSuccessfullyLoadedBeingFalseWhenFileOpened()
{
var workspace = new AdhocWorkspace();
var document = GetDocumentFromIncompleteProject(workspace);
// open document
workspace.OpenDocument(document.Id);
// create listener/service/analyzer
var listener = new AsynchronousOperationListener();
var service = new MyDiagnosticAnalyzerService(new Analyzer(), listener);
var analyzer = service.CreateIncrementalAnalyzer(workspace);
bool syntax = false;
bool semantic = false;
// listen to events
service.DiagnosticsUpdated += (s, a) =>
{
switch (a.Diagnostics.Length)
{
case 0:
return;
case 1:
syntax |= a.Diagnostics[0].Id == Analyzer.s_syntaxRule.Id;
semantic |= a.Diagnostics[0].Id == Analyzer.s_semanticRule.Id;
return;
default:
AssertEx.Fail("shouldn't reach here");
return;
}
};
// now call each analyze method. none of them should run.
await analyzer.AnalyzeSyntaxAsync(document, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, cancellationToken: CancellationToken.None).ConfigureAwait(false);
// wait for all events to raised
await listener.CreateWaitTask().ConfigureAwait(false);
// two should have been called.
Assert.True(syntax);
Assert.True(semantic);
}
private static Document GetDocumentFromIncompleteProject(AdhocWorkspace workspace)
{
var project = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp).WithHasAllInformation(hasAllInformation: false));
return workspace.AddDocument(project.Id, "Empty.cs", SourceText.From(""));
}
private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService
{
internal MyDiagnosticAnalyzerService(DiagnosticAnalyzer analyzer, IAsynchronousOperationListener listener)
: base(new HostAnalyzerManager(
ImmutableArray.Create<AnalyzerReference>(
new TestAnalyzerReferenceByLanguage(
ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>>.Empty.Add(LanguageNames.CSharp, ImmutableArray.Create(analyzer)))),
hostDiagnosticUpdateSource: null),
hostDiagnosticUpdateSource: null,
registrationService: new MockDiagnosticUpdateSourceRegistrationService(),
listener: listener)
{
}
}
private class Analyzer : DiagnosticAnalyzer
{
internal static readonly DiagnosticDescriptor s_syntaxRule = new DiagnosticDescriptor("syntax", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
internal static readonly DiagnosticDescriptor s_semanticRule = new DiagnosticDescriptor("semantic", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
internal static readonly DiagnosticDescriptor s_compilationRule = new DiagnosticDescriptor("compilation", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_syntaxRule, s_semanticRule, s_compilationRule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(c => c.ReportDiagnostic(Diagnostic.Create(s_syntaxRule, c.Tree.GetRoot().GetLocation())));
context.RegisterSemanticModelAction(c => c.ReportDiagnostic(Diagnostic.Create(s_semanticRule, c.SemanticModel.SyntaxTree.GetRoot().GetLocation())));
context.RegisterCompilationAction(c => c.ReportDiagnostic(Diagnostic.Create(s_compilationRule, c.Compilation.SyntaxTrees.First().GetRoot().GetLocation())));
}
}
}
}
......@@ -75,7 +75,7 @@ internal class DiagnosticTaggerWrapper : IDisposable
if (analyzerMap != null || updateSource == null)
{
AnalyzerService = CreateDiagnosticAnalyzerService(analyzerMap, new AggregateAsynchronousOperationListener(_listeners, FeatureAttribute.DiagnosticService));
AnalyzerService = CreateDiagnosticAnalyzerService(analyzerMap, _asyncListener);
}
if (updateSource == null)
......
......@@ -229,6 +229,7 @@
<Compile Include="Diagnostics\AbstractSuppressionDiagnosticTest.cs" />
<Compile Include="Diagnostics\AbstractDiagnosticProviderBasedUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\AbstractUserDiagnosticTest.cs" />
<Compile Include="Diagnostics\DiagnosticAnalyzerServiceTests.cs" />
<Compile Include="Diagnostics\MockDiagnosticUpdateSourceRegistrationService.cs" />
<Compile Include="Diagnostics\TestAnalyzerReferenceByLanguage.cs" />
<Compile Include="Diagnostics\TestDiagnosticAnalyzerDriver.cs" />
......
......@@ -111,7 +111,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename
' Ensure we don't have a partial solution. This is to detect for possible root causes of
' https://github.com/dotnet/roslyn/issues/9298
If currentSolution.Projects.Any(Function(p) Not p.HasCompleteReferencesAsync().Result) Then
If currentSolution.Projects.Any(Function(p) Not p.HasSuccessfullyLoadedAsync().Result) Then
AssertEx.Fail("We have an incomplete project floating around which we should not.")
End If
End Sub
......
......@@ -9,6 +9,7 @@ internal static class IDEDiagnosticIds
public const string SimplifyThisOrMeDiagnosticId = "IDE0003";
public const string RemoveUnnecessaryCastDiagnosticId = "IDE0004";
public const string RemoveUnnecessaryImportsDiagnosticId = "IDE0005";
public const string IntellisenseBuildFailedDiagnosticId = "IDE0006";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -16,6 +17,8 @@ internal abstract class BaseDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer
{
protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
{
Contract.ThrowIfNull(owner);
this.Owner = owner;
this.Workspace = workspace;
this.HostAnalyzerManager = hostAnalyzerManager;
......@@ -27,7 +30,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// <summary>
/// Analyze a single document such that local diagnostics for that document become available,
/// prioritizing analyzing this document over analyzing the rest of the project.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax/semantics), document, and analyzer.
/// </summary>
/// <param name="document">The document to analyze.</param>
......@@ -37,7 +40,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
public abstract Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken);
/// <summary>
/// Analyze a single project such that diagnostics for the entire project become available.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (project), project, and analyzer.
/// </summary>
/// <param name="project">The project to analyze.</param>
......@@ -47,7 +50,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
public abstract Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken);
/// <summary>
/// Apply syntax tree actions (that have not already been applied) to a document.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> for each
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax), document, and analyzer.
/// </summary>
/// <param name="document">The document to analyze.</param>
......@@ -87,14 +90,14 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// <summary>
/// Flush diagnostics produced by a prior analysis of a document,
/// and suppress future analysis of the document.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> with an empty collection.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> with an empty collection.
/// </summary>
/// <param name="documentId"></param>
public abstract void RemoveDocument(DocumentId documentId);
/// <summary>
/// Flush diagnostics produced by a prior analysis of a project,
/// and suppress future analysis of the project.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/> with an empty collection.
/// Calls <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/> with an empty collection.
/// </summary>
/// <param name="projectId"></param>
public abstract void RemoveProject(ProjectId projectId);
......@@ -106,7 +109,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
/// </summary>
/// <param name="solution">The solution.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="includeSuppressedDiagnostics">Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
......@@ -126,7 +129,7 @@ protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Wor
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
/// </summary>
/// <param name="solution">The solution.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(object, DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="id">Matched against values supplied in a <see cref="DiagnosticsUpdatedArgs"/> to <see cref="DiagnosticAnalyzerService.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs)"/>.</param>
/// <param name="includeSuppressedDiagnostics">Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
......@@ -232,7 +235,7 @@ public virtual void LogAnalyzerCountSummary()
// internal for testing purposes.
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> GetOnAnalyzerException(ProjectId projectId)
{
return Owner?.GetOnAnalyzerException(projectId, DiagnosticLogAggregator);
return Owner.GetOnAnalyzerException(projectId, DiagnosticLogAggregator);
}
}
}
......@@ -42,17 +42,30 @@ private DiagnosticAnalyzerService(IDiagnosticUpdateSourceRegistrationService reg
}
}
internal void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
// raise serialized events
// all diagnostics events are serialized.
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
{
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() =>
{
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(asyncToken);
_eventQueue.ScheduleTask(() => ev.RaiseEvent(handler => handler(this, args))).CompletesAsyncOperation(asyncToken);
}
}
internal void RaiseBulkDiagnosticsUpdated(Action<Action<DiagnosticsUpdatedArgs>> eventAction)
{
// all diagnostics events are serialized.
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
{
// we do this bulk update to reduce number of tasks (with captured data) enqueued.
// we saw some "out of memory" due to us having long list of pending tasks in memory.
// this is to reduce for such case to happen.
Action<DiagnosticsUpdatedArgs> raiseEvents = args => ev.RaiseEvent(handler => handler(this, args));
var asyncToken = Listener.BeginAsyncOperation(nameof(RaiseDiagnosticsUpdated));
_eventQueue.ScheduleTask(() => eventAction(raiseEvents)).CompletesAsyncOperation(asyncToken);
}
}
......
......@@ -57,49 +57,76 @@ public DiagnosticService([ImportMany] IEnumerable<Lazy<IAsynchronousOperationLis
private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
{
Contract.ThrowIfNull(sender);
var source = (IDiagnosticUpdateSource)sender;
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (ev.HasHandlers)
if (!RequireRunningEventTasks(source, ev))
{
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
UpdateDataMap(sender, args);
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(eventToken);
return;
}
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
if (!UpdateDataMap(source, args))
{
// there is no change, nothing to raise events for.
return;
}
ev.RaiseEvent(handler => handler(sender, args));
}).CompletesAsyncOperation(eventToken);
}
private void UpdateDataMap(object sender, DiagnosticsUpdatedArgs args)
private bool RequireRunningEventTasks(
IDiagnosticUpdateSource source, EventMap.EventHandlerSet<EventHandler<DiagnosticsUpdatedArgs>> ev)
{
var updateSource = sender as IDiagnosticUpdateSource;
if (updateSource == null)
// basically there are 2 cases when there is no event handler registered.
// first case is when diagnostic update source itself provide GetDiagnostics functionality.
// in that case, DiagnosticService doesn't need to track diagnostics reported. so, it bail out right away.
// second case is when diagnostic source doesn't provide GetDiagnostics functionality.
// in that case, DiagnosticService needs to track diagnostics reported. so it need to enqueue background
// work to process given data regardless whether there is event handler registered or not.
// this could be separated in 2 tasks, but we already saw cases where there are too many tasks enqueued,
// so I merged it to one.
// if it doesn't SupportGetDiagnostics, we need to process reported data, so enqueue task.
if (!source.SupportGetDiagnostics)
{
return;
return true;
}
Contract.Requires(_updateSources.Contains(updateSource));
return ev.HasHandlers;
}
// we expect someone who uses this ability to small.
private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
{
// we expect source who uses this ability to have small number of diagnostics.
lock (_gate)
{
Contract.Requires(_updateSources.Contains(source));
// check cheap early bail out
if (args.Diagnostics.Length == 0 && !_map.ContainsKey(updateSource))
if (args.Diagnostics.Length == 0 && !_map.ContainsKey(source))
{
// no new diagnostic, and we don't have update source for it.
return;
return false;
}
var list = _map.GetOrAdd(updateSource, _ => new Dictionary<object, Data>());
var data = updateSource.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
var diagnosticDataMap = _map.GetOrAdd(source, _ => new Dictionary<object, Data>());
list.Remove(data.Id);
if (list.Count == 0 && args.Diagnostics.Length == 0)
diagnosticDataMap.Remove(args.Id);
if (diagnosticDataMap.Count == 0 && args.Diagnostics.Length == 0)
{
_map.Remove(updateSource);
return;
_map.Remove(source);
return true;
}
list.Add(args.Id, data);
var data = source.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
diagnosticDataMap.Add(args.Id, data);
return true;
}
}
......
......@@ -39,10 +39,18 @@ public DiagnosticState GetState(StateType stateType)
return _state[(int)stateType];
}
public void Remove(object documentOrProjectId)
public void Remove(object documentOrProjectId, bool onlyDocumentStates = false)
{
// this is to clear up states. some caller such as re-analyzing a file wants to
// reset only document related states, some like removing a file wants to clear up
// states all together.
for (var stateType = 0; stateType < s_stateTypeCount; stateType++)
{
if (onlyDocumentStates && stateType == (int)StateType.Project)
{
continue;
}
_state[stateType].Remove(documentOrProjectId);
}
}
......
......@@ -61,13 +61,13 @@ public override Task NewSolutionSnapshotAsync(Solution solution, CancellationTok
public override void RemoveDocument(DocumentId documentId)
{
Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, documentId), Workspace, null, null, null));
}
public override void RemoveProject(ProjectId projectId)
{
Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved(
ValueTuple.Create(this, projectId), Workspace, null, null, null));
}
#endregion
......@@ -213,14 +213,12 @@ private void RaiseEvents(Project project, ImmutableArray<DiagnosticData> diagnos
{
if (kv.Key == null)
{
Owner.RaiseDiagnosticsUpdated(
this, DiagnosticsUpdatedArgs.DiagnosticsCreated(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, project.Id), workspace, solution, project.Id, null, kv.ToImmutableArrayOrEmpty()));
continue;
}
Owner.RaiseDiagnosticsUpdated(
this, DiagnosticsUpdatedArgs.DiagnosticsCreated(
Owner.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsCreated(
ValueTuple.Create(this, kv.Key), workspace, solution, project.Id, kv.Key, kv.ToImmutableArrayOrEmpty()));
}
}
......
......@@ -122,11 +122,7 @@ public int OnImportAddedEx(string filename, string project, CompilerOptions opti
bool embedInteropTypes = optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA;
var properties = new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes);
// The file may not exist, so let's return an error code. In Dev10, we were
// never called us for these at all, and the project system would immediately
// return an S_FALSE. Sadly, returning S_FALSE will convert back to S_OK, which
// would prevent this file from ever being added again. Roslyn bug 7315 for more.
return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filename, properties, hResultForMissingFile: VSConstants.E_FAIL);
return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(filename, properties);
}
public void OnImportRemoved(string filename, string project)
......
......@@ -308,7 +308,7 @@ public ProjectInfo CreateProjectInfoForCurrentState()
{
ValidateReferences();
return ProjectInfo.Create(
var info = ProjectInfo.Create(
this.Id,
this.Version,
this.DisplayName,
......@@ -323,6 +323,8 @@ public ProjectInfo CreateProjectInfoForCurrentState()
projectReferences: _projectReferences,
analyzerReferences: _analyzers.Values.Select(a => a.GetReference()),
additionalDocuments: _additionalDocuments.Values.Select(d => d.GetInitialState()));
return info.WithHasAllInformation(_intellisenseBuildSucceeded);
}
protected ImmutableArray<string> GetStrongNameKeyPaths()
......@@ -352,7 +354,7 @@ public ImmutableArray<ProjectReference> GetCurrentProjectReferences()
{
return ImmutableArray.CreateRange(_projectReferences);
}
public ImmutableArray<VisualStudioMetadataReference> GetCurrentMetadataReferences()
{
return ImmutableArray.CreateRange(_metadataReferences);
......@@ -440,7 +442,7 @@ protected void SetOptions(CompilationOptions compilationOptions, ParseOptions pa
}
}
protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties, int hResultForMissingFile)
protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(string filePath, MetadataReferenceProperties properties)
{
// If this file is coming from a project, then we should convert it to a project reference instead
AbstractProject project;
......@@ -455,13 +457,50 @@ protected int AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(s
}
}
if (!File.Exists(filePath))
{
return hResultForMissingFile;
}
// regardless whether the file exists or not, we still record it. one of reason
// we do that is some cross language p2p references might be resolved
// after they are already reported as metadata references. since we use bin path
// as a way to discover them, if we don't previously record the reference ourselves,
// cross p2p references won't be resolved as p2p references when we finally have
// all required information.
//
// it looks like
// 1. project system sometimes won't guarantee build dependency for intellisense build
// if it is cross language dependency
// 2. output path of referenced cross language project might be changed to right one
// once it is already added as a metadata reference.
//
// but this has one consequence. even if a user adds a project in the solution as
// a metadata reference explicitly, that dll will be automatically converted back to p2p
// reference.
//
// unfortunately there is no way to prevent this using information we have since,
// at this point, we don't know whether it is a metadata reference added because
// we don't have enough information yet for p2p reference or user explicitly added it
// as a metadata reference.
AddMetadataReferenceCore(this.MetadataReferenceProvider.CreateMetadataReference(this, filePath, properties));
// here, we change behavior compared to old C# language service. regardless of file being exist or not,
// we will always return S_OK. this is to support cross language p2p reference better.
//
// this should make project system to cache all cross language p2p references regardless
// whether it actually exist in disk or not.
// (see Roslyn bug 7315 for history - http://vstfdevdiv:8080/DevDiv_Projects/Roslyn/_workitems?_a=edit&id=7315)
//
// after this point, Roslyn will take care of non-exist metadata reference.
//
// But, this doesn't sovle the issue where actual metadata reference
// (not cross language p2p reference) is missing at the time project is opened.
//
// in that case, msbuild filter those actual metadata references out, so project system doesn't know
// path to the reference. since it doesn't know where dll is, it can't (or currently doesn't)
// setup file change notification either to find out when dll becomes available.
//
// at this point, user has 2 ways to recover missing metadata reference once it becomes available.
//
// one way is explicitly clicking that missing reference from solution explorer reference node.
// the other is building the project. at that point, project system will refresh references
// which will discover new dll and connect to us. once it is connected, we will take care of it.
return VSConstants.S_OK;
}
......
// 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.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal partial class AbstractProject : IIntellisenseBuildTarget
{
private static readonly object s_diagnosticKey = new object();
// set default to true so that we maintain old behavior when project system doesn't
// implement IIntellisenseBuildTarget
private bool _intellisenseBuildSucceeded = true;
private string _intellisenseBuildFailureReason = null;
public void SetIntellisenseBuildResult(bool succeeded, string reason)
{
// set intellisense related info
_intellisenseBuildSucceeded = succeeded;
_intellisenseBuildFailureReason = string.IsNullOrWhiteSpace(reason) ? null : reason.Trim();
UpdateHostDiagnostics(succeeded, reason);
if (_pushingChangesToWorkspaceHosts)
{
// set workspace reference info
ProjectTracker.NotifyWorkspaceHosts(host => (host as IVisualStudioWorkspaceHost2)?.OnHasAllInformation(Id, succeeded));
}
}
private void UpdateHostDiagnostics(bool succeeded, string reason)
{
if (!succeeded)
{
// report intellisense build failure to error list
this.HostDiagnosticUpdateSource?.UpdateDiagnosticsForProject(
_id, s_diagnosticKey, SpecializedCollections.SingletonEnumerable(CreateIntellisenseBuildFailureDiagnostic(reason)));
}
else
{
// clear intellisense build failure diagnostic from error list.
this.HostDiagnosticUpdateSource?.ClearDiagnosticsForProject(_id, s_diagnosticKey);
}
}
private DiagnosticData CreateIntellisenseBuildFailureDiagnostic(string reason)
{
// log intellisense build failure
Logger.Log(FunctionId.IntellisenseBuild_Failed, KeyValueLogMessage.Create(m => m["Reason"] = reason ?? string.Empty));
return new DiagnosticData(
IDEDiagnosticIds.IntellisenseBuildFailedDiagnosticId,
FeaturesResources.ErrorCategory,
ServicesVSResources.IntellisenseBuildFailedMessage,
ServicesVSResources.ResourceManager.GetString(nameof(ServicesVSResources.IntellisenseBuildFailedMessage), CodeAnalysis.Diagnostics.Extensions.s_USCultureInfo),
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
warningLevel: 0,
workspace: Workspace,
projectId: Id,
title: ServicesVSResources.IntellisenseBuildFailedTitle,
description: GetDescription(reason),
helpLink: "http://go.microsoft.com/fwlink/p/?LinkID=734719");
}
private string GetDescription(string reason)
{
var logFilePath = $"{Path.GetTempPath()}\\{Path.GetFileNameWithoutExtension(this._filePathOpt)}_*.designtime.log";
var logFileDescription = string.Format(ServicesVSResources.IntellisenseBuildFailedDescription, logFilePath);
if (string.IsNullOrWhiteSpace(reason))
{
return logFileDescription;
}
return string.Join(Environment.NewLine, logFileDescription, string.Empty, ServicesVSResources.IntellisenseBuildFailedDescriptionExtra, reason);
}
}
}
// 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.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;
......@@ -15,6 +11,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal sealed class FileChangeTracker : IVsFileChangeEvents, IDisposable
{
private const uint FileChangeFlags = (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Add | _VSFILECHANGEFLAGS.VSFILECHG_Del | _VSFILECHANGEFLAGS.VSFILECHG_Size);
private static readonly Lazy<uint> s_none = new Lazy<uint>(() => /* value doesn't matter*/ 42424242, LazyThreadSafetyMode.ExecutionAndPublication);
private readonly IVsFileChangeEx _fileChangeService;
......@@ -74,7 +72,7 @@ public void StartFileChangeListeningAsync()
{
uint newCookie;
Marshal.ThrowExceptionForHR(
_fileChangeService.AdviseFileChange(_filePath, (uint)_VSFILECHANGEFLAGS.VSFILECHG_Time, this, out newCookie));
_fileChangeService.AdviseFileChange(_filePath, FileChangeFlags, this, out newCookie));
return newCookie;
}, LazyThreadSafetyMode.ExecutionAndPublication);
......
// 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 Microsoft.CodeAnalysis;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
// TODO: Find a better name for this
internal interface IVisualStudioWorkspaceHost2
{
void OnHasAllInformation(ProjectId projectId, bool hasAllInformation);
}
}
// 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.Runtime.InteropServices;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("F304ABA7-2448-4EE9-A706-D1838F200398")]
internal interface IIntellisenseBuildTarget
{
// currently, reason is not being used.
void SetIntellisenseBuildResult(bool succeeded, [MarshalAs(UnmanagedType.LPWStr)] string reason);
}
}
......@@ -46,7 +46,7 @@ public HostProject(Workspace workspace, SolutionId solutionId, string languageNa
public ProjectInfo CreateProjectInfoForCurrentState()
{
return ProjectInfo.Create(
var info = ProjectInfo.Create(
this.Id,
_version,
name: ServicesVSResources.MiscellaneousFiles,
......@@ -60,6 +60,10 @@ public ProjectInfo CreateProjectInfoForCurrentState()
metadataReferences: _metadataReferences,
isSubmission: false,
hostObjectType: null);
// misc project will never be fully loaded since, by defintion, it won't know
// what the full set of information is.
return info.WithHasAllInformation(hasAllInformation: false);
}
public Microsoft.VisualStudio.Shell.Interop.IVsHierarchy Hierarchy => null;
......
......@@ -14,16 +14,7 @@ void IVsSolutionWorkingFoldersEvents.OnAfterLocationChange(uint location, bool c
}
// notify the working folder change
NotifyWorkspaceHosts(host =>
{
var workingFolder = host as IVisualStudioWorkingFolder;
if (workingFolder == null)
{
return;
}
workingFolder.OnAfterWorkingFolderChange();
});
NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnAfterWorkingFolderChange());
}
void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bool pfCanMoveContent)
......@@ -36,16 +27,7 @@ void IVsSolutionWorkingFoldersEvents.OnQueryLocationChange(uint location, out bo
// notify the working folder change
pfCanMoveContent = true;
NotifyWorkspaceHosts(host =>
{
var workingFolder = host as IVisualStudioWorkingFolder;
if (workingFolder == null)
{
return;
}
workingFolder.OnBeforeWorkingFolderChange();
});
NotifyWorkspaceHosts(host => (host as IVisualStudioWorkingFolder)?.OnBeforeWorkingFolderChange());
}
}
}
......@@ -1154,7 +1154,7 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj
/// A trivial implementation of <see cref="IVisualStudioWorkspaceHost" /> that just
/// forwards the calls down to the underlying Workspace.
/// </summary>
protected class VisualStudioWorkspaceHost : IVisualStudioWorkspaceHost, IVisualStudioWorkingFolder
protected sealed class VisualStudioWorkspaceHost : IVisualStudioWorkspaceHost, IVisualStudioWorkspaceHost2, IVisualStudioWorkingFolder
{
private readonly VisualStudioWorkspaceImpl _workspace;
......@@ -1390,6 +1390,11 @@ void IVisualStudioWorkspaceHost.OnAdditionalDocumentTextUpdatedOnDisk(DocumentId
_workspace.OnAdditionalDocumentTextUpdatedOnDisk(id);
}
void IVisualStudioWorkspaceHost2.OnHasAllInformation(ProjectId projectId, bool hasAllInformation)
{
_workspace.OnHasAllInformationChanged(projectId, hasAllInformation);
}
void IVisualStudioWorkingFolder.OnBeforeWorkingFolderChange()
{
UnregisterPrimarySolutionForPersistentStorage(_workspace.CurrentSolution.Id, synchronousShutdown: true);
......
......@@ -555,6 +555,50 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to To see what caused the issue, please try below.
///
///1. Close Visual Studio
///2. Open a Visual Studio Developer Command Prompt
///3. Set environment variable “TraceDesignTime” to true (set TraceDesignTime=true)
///4. Delete .vs directory/.suo file
///5. Restart VS from the command prompt you set the environment varaible (devenv)
///6. Open the solution
///7. Check &apos;{0}&apos; and look for the failed tasks (FAILED).
/// </summary>
internal static string IntellisenseBuildFailedDescription {
get {
return ResourceManager.GetString("IntellisenseBuildFailedDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Additional information:.
/// </summary>
internal static string IntellisenseBuildFailedDescriptionExtra {
get {
return ResourceManager.GetString("IntellisenseBuildFailedDescriptionExtra", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error encountered while loading the project. Some project features, such as full solution analysis for the failed project and projects that depend on it, have been disabled..
/// </summary>
internal static string IntellisenseBuildFailedMessage {
get {
return ResourceManager.GetString("IntellisenseBuildFailedMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Project loading failed..
/// </summary>
internal static string IntellisenseBuildFailedTitle {
get {
return ResourceManager.GetString("IntellisenseBuildFailedTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installing &apos;{0}&apos; failed.
///
......
......@@ -537,6 +537,26 @@ Use the dropdown to view and switch to other projects this file may belong to.</
<data name="Package_uninstall_failed_0" xml:space="preserve">
<value>Package uninstall failed: {0}</value>
</data>
<data name="IntellisenseBuildFailedMessage" xml:space="preserve">
<value>Error encountered while loading the project. Some project features, such as full solution analysis for the failed project and projects that depend on it, have been disabled.</value>
</data>
<data name="IntellisenseBuildFailedTitle" xml:space="preserve">
<value>Project loading failed.</value>
</data>
<data name="IntellisenseBuildFailedDescription" xml:space="preserve">
<value>To see what caused the issue, please try below.
1. Close Visual Studio
2. Open a Visual Studio Developer Command Prompt
3. Set environment variable “TraceDesignTime” to true (set TraceDesignTime=true)
4. Delete .vs directory/.suo file
5. Restart VS from the command prompt you set the environment varaible (devenv)
6. Open the solution
7. Check '{0}' and look for the failed tasks (FAILED)</value>
</data>
<data name="IntellisenseBuildFailedDescriptionExtra" xml:space="preserve">
<value>Additional information:</value>
</data>
<data name="Installing_0_failed_Additional_information_1" xml:space="preserve">
<value>Installing '{0}' failed.
......
......@@ -85,9 +85,12 @@
<Compile Include="Implementation\Preview\ReferenceChange.AnalyzerReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.ProjectReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.cs" />
<Compile Include="Implementation\ProjectSystem\AbstractProject_IIntellisenseBuildTarget.cs" />
<Compile Include="Implementation\ProjectSystem\AbstractRoslynProject.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\IIntellisenseBuildTarget.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\ICompilerOptionsHostObject.cs" />
<Compile Include="Implementation\ProjectSystem\IVisualStudioWorkingFolder.cs" />
<Compile Include="Implementation\ProjectSystem\IVisualStudioWorkspaceHost2.cs" />
<Compile Include="Implementation\ProjectSystem\MetadataReferences\VisualStudioAnalyzerAssemblyLoaderService.cs" />
<Compile Include="Implementation\ProjectSystem\MetadataReferences\VisualStudioFrameworkAssemblyPathResolverFactory.cs" />
<Compile Include="Implementation\ProjectSystem\RuleSets\RuleSetEventHandler.cs" />
......
......@@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
End Function
Public Function AdviseFileChange(pszMkDocument As String, grfFilter As UInteger, pFCE As IVsFileChangeEvents, ByRef pvsCookie As UInteger) As Integer Implements IVsFileChangeEx.AdviseFileChange
If grfFilter <> _VSFILECHANGEFLAGS.VSFILECHG_Time Then
If (grfFilter And _VSFILECHANGEFLAGS.VSFILECHG_Time) <> _VSFILECHANGEFLAGS.VSFILECHG_Time Then
Throw New NotImplementedException()
End If
......
......@@ -59,7 +59,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
Public Function AddEmbeddedMetaDataReference(wszFileName As String) As Integer Implements IVbCompilerProject.AddEmbeddedMetaDataReference
Try
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True), VSConstants.S_FALSE)
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True))
Catch e As Exception When FilterException(e)
Throw ExceptionUtilities.Unreachable
End Try
......@@ -73,7 +73,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
Return VSConstants.S_OK
End If
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties(), VSConstants.S_FALSE)
Return AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(wszFileName, New MetadataReferenceProperties())
Catch e As Exception When FilterException(e)
Throw ExceptionUtilities.Unreachable
End Try
......@@ -415,7 +415,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim
If HasMetadataReference(newRuntimeLibrary) Then
_explicitlyAddedDefaultReferences.Add(newRuntimeLibrary)
Else
MyBase.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(newRuntimeLibrary, MetadataReferenceProperties.Assembly, hResultForMissingFile:=0)
MyBase.AddMetadataReferenceAndTryConvertingToProjectReferenceIfPossible(newRuntimeLibrary, MetadataReferenceProperties.Assembly)
End If
Next
End If
......
......@@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
{
internal static class Extensions
{
private static readonly CultureInfo s_USCultureInfo = new CultureInfo("en-US");
public static readonly CultureInfo s_USCultureInfo = new CultureInfo("en-US");
public static string GetBingHelpMessage(this Diagnostic diagnostic, Workspace workspace = null)
{
......
......@@ -317,5 +317,6 @@ internal enum FunctionId
CodefixInfobar_EnableAndIgnoreFutureErrors,
CodefixInfobar_LeaveDisabled,
CodefixInfobar_ErrorIgnored,
IntellisenseBuild_Failed,
}
}
......@@ -35,55 +35,26 @@ internal Project(Solution solution, ProjectState projectState)
_projectState = projectState;
}
internal ProjectState State
{
get { return _projectState; }
}
/// <summary>
/// The solution this project is part of.
/// </summary>
public Solution Solution
{
get
{
return _solution;
}
}
public Solution Solution => _solution;
/// <summary>
/// The ID of the project. Multiple <see cref="Project"/> instances may share the same ID. However, only
/// one project may have this ID in any given solution.
/// </summary>
public ProjectId Id
{
get
{
return _projectState.Id;
}
}
public ProjectId Id => _projectState.Id;
/// <summary>
/// The path to the project file or null if there is no project file.
/// </summary>
public string FilePath
{
get
{
return _projectState.FilePath;
}
}
public string FilePath => _projectState.FilePath;
/// <summary>
/// The path to the output file, or null if it is not known.
/// </summary>
public string OutputFilePath
{
get
{
return _projectState.OutputFilePath;
}
}
public string OutputFilePath => _projectState.OutputFilePath;
/// <summary>
/// <code>true</code> if this <see cref="Project"/> supports providing data through the
......@@ -91,195 +62,93 @@ public string OutputFilePath
///
/// If <code>false</code> then this method will return <code>null</code> instead.
/// </summary>
public bool SupportsCompilation
{
get
{
return this.LanguageServices.GetService<ICompilationFactoryService>() != null;
}
}
public bool SupportsCompilation => this.LanguageServices.GetService<ICompilationFactoryService>() != null;
/// <summary>
/// The language services from the host environment associated with this project's language.
/// </summary>
public HostLanguageServices LanguageServices
{
get { return _projectState.LanguageServices; }
}
public HostLanguageServices LanguageServices => _projectState.LanguageServices;
/// <summary>
/// The language associated with the project.
/// </summary>
public string Language
{
get
{
return _projectState.LanguageServices.Language;
}
}
public string Language => _projectState.LanguageServices.Language;
/// <summary>
/// The name of the assembly this project represents.
/// </summary>
public string AssemblyName
{
get
{
return _projectState.AssemblyName;
}
}
public string AssemblyName => _projectState.AssemblyName;
/// <summary>
/// The name of the project. This may be different than the assembly name.
/// </summary>
public string Name
{
get
{
return _projectState.Name;
}
}
public string Name => _projectState.Name;
/// <summary>
/// The list of all other metadata sources (assemblies) that this project references.
/// </summary>
public IReadOnlyList<MetadataReference> MetadataReferences
{
get
{
return _projectState.MetadataReferences;
}
}
public IReadOnlyList<MetadataReference> MetadataReferences => _projectState.MetadataReferences;
/// <summary>
/// The list of all other projects within the same solution that this project references.
/// </summary>
public IEnumerable<ProjectReference> ProjectReferences
{
get
{
return _projectState.ProjectReferences.Where(pr => this.Solution.ContainsProject(pr.ProjectId));
}
}
public IEnumerable<ProjectReference> ProjectReferences => _projectState.ProjectReferences.Where(pr => this.Solution.ContainsProject(pr.ProjectId));
/// <summary>
/// The list of all other projects that this project references, including projects that
/// are not part of the solution.
/// </summary>
public IReadOnlyList<ProjectReference> AllProjectReferences
{
get
{
return _projectState.ProjectReferences;
}
}
public IReadOnlyList<ProjectReference> AllProjectReferences => _projectState.ProjectReferences;
/// <summary>
/// The list of all the diagnostic analyzer references for this project.
/// </summary>
public IReadOnlyList<AnalyzerReference> AnalyzerReferences
{
get
{
return _projectState.AnalyzerReferences;
}
}
public IReadOnlyList<AnalyzerReference> AnalyzerReferences => _projectState.AnalyzerReferences;
/// <summary>
/// The options used by analyzers for this project.
/// </summary>
public AnalyzerOptions AnalyzerOptions
{
get
{
return _projectState.AnalyzerOptions;
}
}
public AnalyzerOptions AnalyzerOptions => _projectState.AnalyzerOptions;
/// <summary>
/// The options used when building the compilation for this project.
/// </summary>
public CompilationOptions CompilationOptions
{
get
{
return _projectState.CompilationOptions;
}
}
public CompilationOptions CompilationOptions => _projectState.CompilationOptions;
/// <summary>
/// The options used when parsing documents for this project.
/// </summary>
public ParseOptions ParseOptions
{
get
{
return _projectState.ParseOptions;
}
}
public ParseOptions ParseOptions => _projectState.ParseOptions;
/// <summary>
/// Returns true if this is a submission project.
/// </summary>
public bool IsSubmission
{
get
{
return _projectState.IsSubmission;
}
}
public bool IsSubmission => _projectState.IsSubmission;
/// <summary>
/// True if the project has any documents.
/// </summary>
public bool HasDocuments
{
get
{
return _projectState.HasDocuments;
}
}
public bool HasDocuments => _projectState.HasDocuments;
/// <summary>
/// All the document IDs associated with this project.
/// </summary>
public IReadOnlyList<DocumentId> DocumentIds
{
get
{
return _projectState.DocumentIds;
}
}
public IReadOnlyList<DocumentId> DocumentIds => _projectState.DocumentIds;
/// <summary>
/// All the additional document IDs associated with this project.
/// </summary>
public IReadOnlyList<DocumentId> AdditionalDocumentIds
{
get
{
return _projectState.AdditionalDocumentIds;
}
}
public IReadOnlyList<DocumentId> AdditionalDocumentIds => _projectState.AdditionalDocumentIds;
/// <summary>
/// All the documents associated with this project.
/// </summary>
public IEnumerable<Document> Documents
{
get
{
return _projectState.DocumentIds.Select(GetDocument);
}
}
public IEnumerable<Document> Documents => _projectState.DocumentIds.Select(GetDocument);
public IEnumerable<TextDocument> AdditionalDocuments
{
get
{
return _projectState.AdditionalDocumentIds.Select(GetAdditionalDocument);
}
}
/// <summary>
/// All the additional documents associated with this project.
/// </summary>
public IEnumerable<TextDocument> AdditionalDocuments => _projectState.AdditionalDocumentIds.Select(GetAdditionalDocument);
/// <summary>
/// True if the project contains a document with the specified ID.
......@@ -390,12 +259,12 @@ public Task<Compilation> GetCompilationAsync(CancellationToken cancellationToken
}
/// <summary>
/// Determines if the compilation returned by <see cref="GetCompilationAsync"/> has all the references it's expected to have.
/// Determines if the compilation returned by <see cref="GetCompilationAsync"/> and all its referenced compilaton are from fully loaded projects.
/// </summary>
// TODO: make this public
internal Task<bool> HasCompleteReferencesAsync(CancellationToken cancellationToken = default(CancellationToken))
internal Task<bool> HasSuccessfullyLoadedAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return _solution.HasCompleteReferencesAsync(this, cancellationToken);
return _solution.HasSuccessfullyLoadedAsync(this, cancellationToken);
}
/// <summary>
......
......@@ -2,8 +2,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
using System.Diagnostics;
......@@ -96,6 +94,13 @@ public sealed class ProjectInfo
/// </summary>
public Type HostObjectType { get; }
/// <summary>
/// True if project information is complete. In some workspace hosts, it is possible
/// a project only has partial information. In such cases, a project might not have all
/// information on its files or references.
/// </summary>
internal bool HasAllInformation { get; }
private ProjectInfo(
ProjectId id,
VersionStamp version,
......@@ -112,7 +117,8 @@ public sealed class ProjectInfo
IEnumerable<AnalyzerReference> analyzerReferences,
IEnumerable<DocumentInfo> additionalDocuments,
bool isSubmission,
Type hostObjectType)
Type hostObjectType,
bool hasAllInformation)
{
if (id == null)
{
......@@ -150,28 +156,30 @@ public sealed class ProjectInfo
this.AdditionalDocuments = additionalDocuments.ToImmutableReadOnlyListOrEmpty();
this.IsSubmission = isSubmission;
this.HostObjectType = hostObjectType;
this.HasAllInformation = hasAllInformation;
}
/// <summary>
/// Create a new instance of a ProjectInfo.
/// </summary>
public static ProjectInfo Create(
internal static ProjectInfo Create(
ProjectId id,
VersionStamp version,
string name,
string assemblyName,
string language,
string filePath = null,
string outputFilePath = null,
CompilationOptions compilationOptions = null,
ParseOptions parseOptions = null,
IEnumerable<DocumentInfo> documents = null,
IEnumerable<ProjectReference> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
bool isSubmission = false,
Type hostObjectType = null)
string filePath,
string outputFilePath,
CompilationOptions compilationOptions,
ParseOptions parseOptions,
IEnumerable<DocumentInfo> documents,
IEnumerable<ProjectReference> projectReferences,
IEnumerable<MetadataReference> metadataReferences,
IEnumerable<AnalyzerReference> analyzerReferences,
IEnumerable<DocumentInfo> additionalDocuments,
bool isSubmission,
Type hostObjectType,
bool hasAllInformation)
{
return new ProjectInfo(
id,
......@@ -189,7 +197,36 @@ public sealed class ProjectInfo
analyzerReferences,
additionalDocuments,
isSubmission,
hostObjectType);
hostObjectType,
hasAllInformation);
}
/// <summary>
/// Create a new instance of a ProjectInfo.
/// </summary>
public static ProjectInfo Create(
ProjectId id,
VersionStamp version,
string name,
string assemblyName,
string language,
string filePath = null,
string outputFilePath = null,
CompilationOptions compilationOptions = null,
ParseOptions parseOptions = null,
IEnumerable<DocumentInfo> documents = null,
IEnumerable<ProjectReference> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
bool isSubmission = false,
Type hostObjectType = null)
{
return Create(
id, version, name, assemblyName, language,
filePath, outputFilePath, compilationOptions, parseOptions,
documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments,
isSubmission, hostObjectType, hasAllInformation: true);
}
private ProjectInfo With(
......@@ -208,7 +245,8 @@ public sealed class ProjectInfo
IEnumerable<AnalyzerReference> analyzerReferences = null,
IEnumerable<DocumentInfo> additionalDocuments = null,
Optional<bool> isSubmission = default(Optional<bool>),
Optional<Type> hostObjectType = default(Optional<Type>))
Optional<Type> hostObjectType = default(Optional<Type>),
Optional<bool> hasAllInformation = default(Optional<bool>))
{
var newId = id ?? this.Id;
var newVersion = version.HasValue ? version.Value : this.Version;
......@@ -226,6 +264,7 @@ public sealed class ProjectInfo
var newAdditionalDocuments = additionalDocuments ?? this.AdditionalDocuments;
var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : this.IsSubmission;
var newHostObjectType = hostObjectType.HasValue ? hostObjectType.Value : this.HostObjectType;
var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : this.HasAllInformation;
if (newId == this.Id &&
newVersion == this.Version &&
......@@ -242,7 +281,8 @@ public sealed class ProjectInfo
newAnalyzerReferences == this.AnalyzerReferences &&
newAdditionalDocuments == this.AdditionalDocuments &&
newIsSubmission == this.IsSubmission &&
newHostObjectType == this.HostObjectType)
newHostObjectType == this.HostObjectType &&
newHasAllInformation == this.HasAllInformation)
{
return this;
}
......@@ -263,7 +303,8 @@ public sealed class ProjectInfo
newAnalyzerReferences,
newAdditionalDocuments,
newIsSubmission,
newHostObjectType);
newHostObjectType,
newHasAllInformation);
}
public ProjectInfo WithDocuments(IEnumerable<DocumentInfo> documents)
......@@ -326,6 +367,11 @@ public ProjectInfo WithAnalyzerReferences(IEnumerable<AnalyzerReference> analyze
return this.With(analyzerReferences: analyzerReferences.ToImmutableReadOnlyListOrEmpty());
}
internal ProjectInfo WithHasAllInformation(bool hasAllInformation)
{
return this.With(hasAllInformation: hasAllInformation);
}
internal string GetDebuggerDisplay()
{
return nameof(ProjectInfo) + " " + Name + (!string.IsNullOrWhiteSpace(FilePath) ? " " + FilePath : "");
......
......@@ -25,7 +25,9 @@ internal partial class ProjectState
private readonly IReadOnlyList<DocumentId> _additionalDocumentIds;
private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentVersion;
private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentTopLevelChangeVersion;
private AnalyzerOptions _analyzerOptions;
// this will be initialized lazily.
private AnalyzerOptions _analyzerOptionsDoNotAccessDirectly;
private ProjectState(
ProjectInfo projectInfo,
......@@ -61,12 +63,12 @@ internal ProjectState(ProjectInfo projectInfo, HostLanguageServices languageServ
_projectInfo = FixProjectInfo(projectInfo);
_documentIds = _projectInfo.Documents.Select(d => d.Id).ToImmutableArray();
_additionalDocumentIds = this.ProjectInfo.AdditionalDocuments.Select(d => d.Id).ToImmutableArray();
_additionalDocumentIds = _projectInfo.AdditionalDocuments.Select(d => d.Id).ToImmutableArray();
var docStates = ImmutableDictionary.CreateRange<DocumentId, DocumentState>(
_projectInfo.Documents.Select(d =>
new KeyValuePair<DocumentId, DocumentState>(d.Id,
CreateDocument(this.ProjectInfo, d, languageServices, solutionServices))));
CreateDocument(_projectInfo, d, languageServices, solutionServices))));
_documentStates = docStates;
......@@ -231,12 +233,12 @@ public AnalyzerOptions AnalyzerOptions
{
get
{
if (_analyzerOptions == null)
if (_analyzerOptionsDoNotAccessDirectly == null)
{
_analyzerOptions = new AnalyzerOptions(_additionalDocumentStates.Values.Select(d => new AdditionalTextDocument(d)).ToImmutableArray<AdditionalText>());
_analyzerOptionsDoNotAccessDirectly = new AnalyzerOptions(_additionalDocumentStates.Values.Select(d => new AdditionalTextDocument(d)).ToImmutableArray<AdditionalText>());
}
return _analyzerOptions;
return _analyzerOptionsDoNotAccessDirectly;
}
}
......@@ -303,6 +305,12 @@ public IReadOnlyList<ProjectReference> ProjectReferences
get { return this.ProjectInfo.ProjectReferences; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public bool HasAllInformation
{
get { return this.ProjectInfo.HasAllInformation; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public bool HasDocuments
{
......@@ -457,6 +465,16 @@ public ProjectState UpdateParseOptions(ParseOptions options)
documentStates: docMap);
}
public ProjectState UpdateHasAllInformation(bool hasAllInformation)
{
if (hasAllInformation == this.HasAllInformation)
{
return this;
}
return this.With(projectInfo: this.ProjectInfo.WithHasAllInformation(hasAllInformation).WithVersion(this.Version.GetNewerVersion()));
}
public static bool IsSameLanguage(ProjectState project1, ProjectState project2)
{
return project1.LanguageServices == project2.LanguageServices;
......
......@@ -39,10 +39,10 @@ private class State
public ValueSource<Compilation> Compilation { get; }
/// <summary>
/// Specifies if there are references that got dropped in the production of <see cref="FinalCompilation"/>. This can return
/// Specifies whether <see cref="FinalCompilation"/> and all compilations it depends on contain full information or not. This can return
/// null if the state isn't at the point where it would know, and it's necessary to transition to <see cref="FinalState"/> to figure that out.
/// </summary>
public virtual bool? HasCompleteReferences => null;
public virtual bool? HasSuccessfullyLoadedTransitively => null;
/// <summary>
/// The final compilation if available, otherwise an empty <see cref="ValueSource{Compilation}"/>.
......@@ -135,19 +135,15 @@ public FullDeclarationState(Compilation declarationCompilation)
/// </summary>
private sealed class FinalState : State
{
private readonly bool _hasCompleteReferences;
private readonly bool _hasSuccessfullyLoadedTransitively;
public override ValueSource<Compilation> FinalCompilation
{
get { return this.Compilation; }
}
public override bool? HasCompleteReferences => _hasCompleteReferences;
public override bool? HasSuccessfullyLoadedTransitively => _hasSuccessfullyLoadedTransitively;
public override ValueSource<Compilation> FinalCompilation => this.Compilation;
public FinalState(ValueSource<Compilation> finalCompilationSource, bool hasCompleteReferences)
public FinalState(ValueSource<Compilation> finalCompilationSource, bool hasSuccessfullyLoadedTransitively)
: base(finalCompilationSource, finalCompilationSource.GetValue().Clone().RemoveAllReferences())
{
_hasCompleteReferences = hasCompleteReferences;
_hasSuccessfullyLoadedTransitively = hasSuccessfullyLoadedTransitively;
}
}
}
......
......@@ -181,7 +181,7 @@ public CompilationTracker FreezePartialStateWithTree(Solution solution, Document
// have the compilation immediately disappear. So we force it to stay around with a ConstantValueSource.
// As a policy, all partial-state projects are said to have incomplete references, since the state has no guarantees.
return new CompilationTracker(inProgressProject,
new FinalState(new ConstantValueSource<Compilation>(inProgressCompilation), hasCompleteReferences: false));
new FinalState(new ConstantValueSource<Compilation>(inProgressCompilation), hasSuccessfullyLoadedTransitively: false));
}
/// <summary>
......@@ -313,21 +313,6 @@ public Task<Compilation> GetCompilationAsync(Solution solution, CancellationToke
}
}
public Task<bool> HasCompleteReferencesAsync(Solution solution, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.HasCompleteReferences.HasValue)
{
return Task.FromResult(state.HasCompleteReferences.Value);
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasCompleteReferences, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
private static string LogBuildCompilationAsync(ProjectState state)
{
return string.Join(",", state.AssemblyName, state.DocumentIds.Count);
......@@ -406,7 +391,7 @@ private async Task<Compilation> GetOrBuildDeclarationCompilationAsync(Solution s
var finalCompilation = state.FinalCompilation.GetValue(cancellationToken);
if (finalCompilation != null)
{
return new CompilationInfo(finalCompilation, hasCompleteReferences: state.HasCompleteReferences.Value);
return new CompilationInfo(finalCompilation, state.HasSuccessfullyLoadedTransitively.Value);
}
// Otherwise, we actually have to build it. Ensure that only one thread is trying to
......@@ -447,7 +432,7 @@ private async Task<Compilation> GetOrBuildDeclarationCompilationAsync(Solution s
var compilation = state.FinalCompilation.GetValue(cancellationToken);
if (compilation != null)
{
return Task.FromResult(new CompilationInfo(compilation, state.HasCompleteReferences.Value));
return Task.FromResult(new CompilationInfo(compilation, state.HasSuccessfullyLoadedTransitively.Value));
}
compilation = state.Compilation.GetValue(cancellationToken);
......@@ -582,12 +567,12 @@ private Compilation CreateEmptyCompilation()
private struct CompilationInfo
{
public Compilation Compilation { get; }
public bool HasCompleteReferences { get; }
public bool HasSuccessfullyLoadedTransitively { get; }
public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
public CompilationInfo(Compilation compilation, bool hasSuccessfullyLoadedTransitively)
{
this.Compilation = compilation;
this.HasCompleteReferences = hasCompleteReferences;
this.HasSuccessfullyLoadedTransitively = hasSuccessfullyLoadedTransitively;
}
}
......@@ -602,7 +587,9 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
{
try
{
bool hasCompleteReferences = true;
// if HasAllInformation is false, then this project is always not completed.
bool hasSuccessfullyLoaded = this.ProjectState.HasAllInformation;
var newReferences = new List<MetadataReference>();
newReferences.AddRange(this.ProjectState.MetadataReferences);
......@@ -639,7 +626,7 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
}
else
{
hasCompleteReferences = false;
hasSuccessfullyLoaded = false;
}
}
}
......@@ -650,9 +637,10 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
compilation = compilation.WithReferences(newReferences);
}
this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasCompleteReferences), solution);
bool hasSuccessfullyLoadedTransitively = !HasMissingReferences(compilation, this.ProjectState.MetadataReferences) && await ComputeHasSuccessfullyLoadedTransitivelyAsync(solution, hasSuccessfullyLoaded, cancellationToken).ConfigureAwait(false);
return new CompilationInfo(compilation, hasCompleteReferences);
this.WriteState(new FinalState(State.CreateValueSource(compilation, solution.Services), hasSuccessfullyLoadedTransitively), solution);
return new CompilationInfo(compilation, hasSuccessfullyLoadedTransitively);
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
......@@ -660,6 +648,43 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
}
}
private bool HasMissingReferences(Compilation compilation, IReadOnlyList<MetadataReference> metadataReferences)
{
foreach (var reference in metadataReferences)
{
if (compilation.GetAssemblyOrModuleSymbol(reference) == null)
{
return true;
}
}
return false;
}
private async Task<bool> ComputeHasSuccessfullyLoadedTransitivelyAsync(Solution solution, bool hasSuccessfullyLoaded, CancellationToken cancellationToken)
{
if (!hasSuccessfullyLoaded)
{
return false;
}
foreach (var projectReference in this.ProjectState.ProjectReferences)
{
var project = solution.GetProject(projectReference.ProjectId);
if (project == null)
{
return false;
}
if (!await solution.HasSuccessfullyLoadedAsync(project, cancellationToken).ConfigureAwait(false))
{
return false;
}
}
return true;
}
/// <summary>
/// Get a metadata reference to this compilation info's compilation with respect to
/// another project. For cross language references produce a skeletal assembly. If the
......@@ -794,6 +819,21 @@ public IEnumerable<SyntaxTree> GetSyntaxTreesWithNameFromDeclarationOnlyCompilat
return clone.GetSymbolsWithName(predicate, filter, cancellationToken).SelectMany(s => s.DeclaringSyntaxReferences.Select(r => r.SyntaxTree));
}
public Task<bool> HasSuccessfullyLoadedAsync(Solution solution, CancellationToken cancellationToken)
{
var state = this.ReadState();
if (state.HasSuccessfullyLoadedTransitively.HasValue)
{
return state.HasSuccessfullyLoadedTransitively.Value ? SpecializedTasks.True : SpecializedTasks.False;
}
else
{
return GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken)
.ContinueWith(t => t.Result.HasSuccessfullyLoadedTransitively, cancellationToken, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
}
}
#region Versions
// Dependent Versions are stored on compilation tracker so they are more likely to survive when unrelated solution branching occurs.
......
......@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
......@@ -839,6 +837,32 @@ public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions option
}
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified hasAllInformation.
/// </summary>
// TODO: make it public
internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformation)
{
if (projectId == null)
{
throw new ArgumentNullException(nameof(projectId));
}
Contract.Requires(this.ContainsProject(projectId));
var oldProject = this.GetProjectState(projectId);
var newProject = oldProject.UpdateHasAllInformation(hasAllInformation);
if (oldProject == newProject)
{
return this;
}
// fork without any change on compilation.
return this.ForkProject(newProject);
}
private static async Task<Compilation> ReplaceSyntaxTreesWithTreesFromNewProjectStateAsync(Compilation compilation, ProjectState projectState, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(capacity: projectState.DocumentIds.Count);
......@@ -2020,11 +2044,16 @@ internal Task<Compilation> GetCompilationAsync(Project project, CancellationToke
: SpecializedTasks.Default<Compilation>();
}
internal Task<bool> HasCompleteReferencesAsync(Project project, CancellationToken cancellationToken)
/// <summary>
/// Return reference completeness for the given project and all projects this references.
/// </summary>
internal Task<bool> HasSuccessfullyLoadedAsync(Project project, CancellationToken cancellationToken)
{
// return HasAllInformation when compilation is not supported.
// regardless whether project support compilation or not, if projectInfo is not complete, we can't gurantee its reference completeness
return project.SupportsCompilation
? this.GetCompilationTracker(project.Id).HasCompleteReferencesAsync(this, cancellationToken)
: SpecializedTasks.False;
? this.GetCompilationTracker(project.Id).HasSuccessfullyLoadedAsync(this, cancellationToken)
: project.Solution.GetProjectState(project.Id).HasAllInformation ? SpecializedTasks.True : SpecializedTasks.False;
}
private static readonly ConditionalWeakTable<MetadataReference, ProjectId> s_metadataReferenceToProjectMap =
......
......@@ -703,6 +703,25 @@ protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documen
}
}
/// <summary>
/// Call this method when status of project has changed to incomplete.
/// See <see cref="ProjectInfo.HasAllInformation"/> for more information.
/// </summary>
// TODO: make it public
internal void OnHasAllInformationChanged(ProjectId projectId, bool hasAllInformation)
{
using (_serializationLock.DisposableWait())
{
CheckProjectIsInCurrentSolution(projectId);
// if state is different than what we have
var oldSolution = this.CurrentSolution;
var newSolution = this.SetCurrentSolution(oldSolution.WithHasAllInformation(projectId, hasAllInformation));
this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.ProjectChanged, oldSolution, newSolution, projectId);
}
}
/// <summary>
/// Call this method when the text of a document is updated in the host environment.
/// </summary>
......
......@@ -1404,8 +1404,8 @@ public void TestProjectWithNoBrokenReferencesHasNoIncompleteReferences()
projectReferences: new[] { new ProjectReference(project1.Id) }));
// Nothing should have incomplete references, and everything should build
Assert.True(project1.HasCompleteReferencesAsync().Result);
Assert.True(project2.HasCompleteReferencesAsync().Result);
Assert.True(project1.HasSuccessfullyLoadedAsync().Result);
Assert.True(project2.HasSuccessfullyLoadedAsync().Result);
Assert.Single(project2.GetCompilationAsync().Result.ExternalReferences);
}
......@@ -1425,8 +1425,8 @@ public void TestProjectWithBrokenCrossLanguageReferenceHasIncompleteReferences()
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(project1.Id) }));
Assert.True(project1.HasCompleteReferencesAsync().Result);
Assert.False(project2.HasCompleteReferencesAsync().Result);
Assert.True(project1.HasSuccessfullyLoadedAsync().Result);
Assert.False(project2.HasSuccessfullyLoadedAsync().Result);
Assert.Empty(project2.GetCompilationAsync().Result.ExternalReferences);
}
......@@ -1450,8 +1450,103 @@ public void TestFrozenPartialProjectAlwaysIsIncomplete()
// Nothing should have incomplete references, and everything should build
var frozenSolution = document.WithFrozenPartialSemanticsAsync(CancellationToken.None).Result.Project.Solution;
Assert.True(frozenSolution.GetProject(project1.Id).HasCompleteReferencesAsync().Result);
Assert.True(frozenSolution.GetProject(project2.Id).HasCompleteReferencesAsync().Result);
Assert.True(frozenSolution.GetProject(project1.Id).HasSuccessfullyLoadedAsync().Result);
Assert.True(frozenSolution.GetProject(project2.Id).HasSuccessfullyLoadedAsync().Result);
}
[Fact]
public void TestProjectCompletenessWithMultipleProjects()
{
Project csBrokenProject;
Project vbNormalProject;
Project dependsOnBrokenProject;
Project dependsOnVbNormalProject;
Project transitivelyDependsOnBrokenProjects;
Project transitivelyDependsOnNormalProjects;
GetMultipleProjects(out csBrokenProject, out vbNormalProject, out dependsOnBrokenProject, out dependsOnVbNormalProject, out transitivelyDependsOnBrokenProjects, out transitivelyDependsOnNormalProjects);
// check flag for a broken project itself
Assert.False(csBrokenProject.HasSuccessfullyLoadedAsync().Result);
// check flag for a normal project itself
Assert.True(vbNormalProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that directly reference a broken project
Assert.False(dependsOnBrokenProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that directly reference only normal project
Assert.True(dependsOnVbNormalProject.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that indirectly reference a borken project
// normal project -> normal project -> broken project
Assert.False(transitivelyDependsOnBrokenProjects.HasSuccessfullyLoadedAsync().Result);
// check flag for normal project that indirectly reference only normal project
// normal project -> normal project -> normal project
Assert.True(transitivelyDependsOnNormalProjects.HasSuccessfullyLoadedAsync().Result);
}
private static void GetMultipleProjects(
out Project csBrokenProject,
out Project vbNormalProject,
out Project dependsOnBrokenProject,
out Project dependsOnVbNormalProject,
out Project transitivelyDependsOnBrokenProjects,
out Project transitivelyDependsOnNormalProjects)
{
var workspace = new AdhocWorkspace();
csBrokenProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp).WithHasAllInformation(hasAllInformation: false));
vbNormalProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic));
dependsOnBrokenProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(csBrokenProject.Id), new ProjectReference(vbNormalProject.Id) }));
dependsOnVbNormalProject = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp,
projectReferences: new[] { new ProjectReference(vbNormalProject.Id) }));
transitivelyDependsOnBrokenProjects = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"CSharpProject",
"CSharpProject",
LanguageNames.CSharp,
projectReferences: new[] { new ProjectReference(dependsOnBrokenProject.Id) }));
transitivelyDependsOnNormalProjects = workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"VisualBasicProject",
"VisualBasicProject",
LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(dependsOnVbNormalProject.Id) }));
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册