|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Collections.Immutable;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents a set of projects and their source code documents.
/// </summary>
public partial class Solution
{
// SolutionState that doesn't hold onto Project/Document
private readonly SolutionState _state;
// Values for all these are created on demand.
private ImmutableHashMap<ProjectId, Project> _projectIdToProjectMap;
private Solution(SolutionState state)
{
_projectIdToProjectMap = ImmutableHashMap<ProjectId, Project>.Empty;
_state = state;
}
internal Solution(Workspace workspace, SolutionInfo.SolutionAttributes solutionAttributes, SolutionOptionSet options, IReadOnlyList<AnalyzerReference> analyzerReferences)
: this(new SolutionState(workspace.Kind, workspace.PartialSemanticsEnabled, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences))
{
}
internal SolutionState State => _state;
internal int WorkspaceVersion => _state.WorkspaceVersion;
internal bool PartialSemanticsEnabled => _state.PartialSemanticsEnabled;
/// <summary>
/// Per solution services provided by the host environment. Use this instead of <see
/// cref="Workspace.Services"/> when possible.
/// </summary>
public SolutionServices Services => _state.Services;
internal string? WorkspaceKind => _state.WorkspaceKind;
internal ProjectState? GetProjectState(ProjectId projectId) => _state.GetProjectState(projectId);
/// <summary>
/// The Workspace this solution is associated with.
/// </summary>
public Workspace Workspace
{
get
{
Contract.ThrowIfTrue(this.WorkspaceKind == CodeAnalysis.WorkspaceKind.RemoteWorkspace, "Access .Workspace off of a RemoteWorkspace Solution is not supported.");
#pragma warning disable CS0618 // Type or member is obsolete (TODO: obsolete the property)
return _state.Services.WorkspaceServices.Workspace;
#pragma warning restore
}
}
/// <summary>
/// The Id of the solution. Multiple solution instances may share the same Id.
/// </summary>
public SolutionId Id => _state.Id;
/// <summary>
/// The path to the solution file or null if there is no solution file.
/// </summary>
public string? FilePath => _state.FilePath;
/// <summary>
/// The solution version. This equates to the solution file's version.
/// </summary>
public VersionStamp Version => _state.Version;
/// <summary>
/// A list of all the ids for all the projects contained by the solution.
/// </summary>
public IReadOnlyList<ProjectId> ProjectIds => _state.ProjectIds;
/// <summary>
/// A list of all the projects contained by the solution.
/// </summary>
public IEnumerable<Project> Projects => ProjectIds.Select(id => GetProject(id)!);
/// <summary>
/// The version of the most recently modified project.
/// </summary>
public VersionStamp GetLatestProjectVersion() => _state.GetLatestProjectVersion();
/// <summary>
/// True if the solution contains a project with the specified project ID.
/// </summary>
public bool ContainsProject([NotNullWhen(returnValue: true)] ProjectId? projectId) => _state.ContainsProject(projectId);
/// <summary>
/// Gets the project in this solution with the specified project ID.
///
/// If the id is not an id of a project that is part of this solution the method returns null.
/// </summary>
public Project? GetProject(ProjectId? projectId)
{
if (this.ContainsProject(projectId))
{
return ImmutableHashMapExtensions.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this);
}
return null;
}
private static readonly Func<ProjectId, Solution, Project> s_createProjectFunction = CreateProject;
private static Project CreateProject(ProjectId projectId, Solution solution)
{
var state = solution.State.GetProjectState(projectId);
Contract.ThrowIfNull(state);
return new Project(solution, state);
}
#pragma warning disable IDE0060 // Remove unused parameter 'cancellationToken' - shipped public API
/// <summary>
/// Gets the <see cref="Project"/> associated with an assembly symbol.
/// </summary>
public Project? GetProject(IAssemblySymbol assemblySymbol,
CancellationToken cancellationToken = default)
#pragma warning restore IDE0060 // Remove unused parameter
{
var projectState = _state.GetProjectState(assemblySymbol);
return projectState == null ? null : GetProject(projectState.Id);
}
/// <summary>
/// Given a <paramref name="symbol"/> returns the <see cref="ProjectId"/> of the <see cref="Project"/> it came
/// from. Returns <see langword="null"/> if <paramref name="symbol"/> does not come from any project in this solution.
/// </summary>
/// <remarks>
/// This function differs from <see cref="GetProject(IAssemblySymbol, CancellationToken)"/> in terms of how it
/// treats <see cref="IAssemblySymbol"/>s. Specifically, say there is the following:
///
/// <c>
/// Project-A, containing Symbol-A.<para/>
/// Project-B, with a reference to Project-A, and usage of Symbol-A.
/// </c>
///
/// It is possible (with retargeting, and other complex cases) that Symbol-A from Project-B will be a different
/// symbol than Symbol-A from Project-A. However, <see cref="GetProject(IAssemblySymbol, CancellationToken)"/>
/// will always try to return Project-A for either of the Symbol-A's, as it prefers to return the original
/// Source-Project of the original definition, not the project that actually produced the symbol. For many
/// features this is an acceptable abstraction. However, for some cases (Find-References in particular) it is
/// necessary to resolve symbols back to the actual project/compilation that produced them for correctness.
/// </remarks>
internal ProjectId? GetOriginatingProjectId(ISymbol symbol)
=> _state.GetOriginatingProjectId(symbol);
/// <inheritdoc cref="GetOriginatingProjectId"/>
internal Project? GetOriginatingProject(ISymbol symbol)
=> GetProject(GetOriginatingProjectId(symbol));
/// <summary>
/// True if the solution contains the document in one of its projects
/// </summary>
public bool ContainsDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => _state.ContainsDocument(documentId);
/// <summary>
/// True if the solution contains the additional document in one of its projects
/// </summary>
public bool ContainsAdditionalDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => _state.ContainsAdditionalDocument(documentId);
/// <summary>
/// True if the solution contains the analyzer config document in one of its projects
/// </summary>
public bool ContainsAnalyzerConfigDocument([NotNullWhen(returnValue: true)] DocumentId? documentId) => _state.ContainsAnalyzerConfigDocument(documentId);
/// <summary>
/// Gets the documentId in this solution with the specified syntax tree.
/// </summary>
public DocumentId? GetDocumentId(SyntaxTree? syntaxTree) => GetDocumentId(syntaxTree, projectId: null);
/// <summary>
/// Gets the documentId in this solution with the specified syntax tree.
/// </summary>
public DocumentId? GetDocumentId(SyntaxTree? syntaxTree, ProjectId? projectId)
{
return State.GetDocumentState(syntaxTree, projectId)?.Id;
}
/// <summary>
/// Gets the document in this solution with the specified document ID.
/// </summary>
public Document? GetDocument(DocumentId? documentId)
=> GetProject(documentId?.ProjectId)?.GetDocument(documentId!);
/// <summary>
/// Gets a document or a source generated document in this solution with the specified document ID.
/// </summary>
internal ValueTask<Document?> GetDocumentAsync(DocumentId? documentId, bool includeSourceGenerated = false, CancellationToken cancellationToken = default)
{
var project = GetProject(documentId?.ProjectId);
if (project == null)
{
return default;
}
Contract.ThrowIfNull(documentId);
return project.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken);
}
/// <summary>
/// Gets a document, additional document, analyzer config document or a source generated document in this solution with the specified document ID.
/// </summary>
internal ValueTask<TextDocument?> GetTextDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken = default)
{
var project = GetProject(documentId?.ProjectId);
if (project == null)
{
return default;
}
Contract.ThrowIfNull(documentId);
return project.GetTextDocumentAsync(documentId, cancellationToken);
}
/// <summary>
/// Gets the additional document in this solution with the specified document ID.
/// </summary>
public TextDocument? GetAdditionalDocument(DocumentId? documentId)
{
if (this.ContainsAdditionalDocument(documentId))
{
return this.GetProject(documentId.ProjectId)!.GetAdditionalDocument(documentId);
}
return null;
}
/// <summary>
/// Gets the analyzer config document in this solution with the specified document ID.
/// </summary>
public AnalyzerConfigDocument? GetAnalyzerConfigDocument(DocumentId? documentId)
{
if (this.ContainsAnalyzerConfigDocument(documentId))
{
return this.GetProject(documentId.ProjectId)!.GetAnalyzerConfigDocument(documentId);
}
return null;
}
public ValueTask<SourceGeneratedDocument?> GetSourceGeneratedDocumentAsync(DocumentId documentId, CancellationToken cancellationToken)
{
var project = GetProject(documentId.ProjectId);
if (project == null)
{
return new(result: null);
}
else
{
return project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken);
}
}
/// <summary>
/// Gets the document in this solution with the specified syntax tree.
/// </summary>
public Document? GetDocument(SyntaxTree? syntaxTree)
=> this.GetDocument(syntaxTree, projectId: null);
internal Document? GetDocument(SyntaxTree? syntaxTree, ProjectId? projectId)
{
if (syntaxTree != null)
{
var documentState = State.GetDocumentState(syntaxTree, projectId);
if (documentState is SourceGeneratedDocumentState)
{
// We have the underlying state, but we need to get the wrapper SourceGeneratedDocument object. The wrapping is maintained by
// the Project object, so we'll now fetch the project and ask it to get the SourceGeneratedDocument wrapper. Under the covers this
// implicity may call to fetch the SourceGeneratedDocumentState a second time but that's not expensive.
var generatedDocument = this.GetRequiredProject(documentState.Id.ProjectId).TryGetSourceGeneratedDocumentForAlreadyGeneratedId(documentState.Id);
Contract.ThrowIfNull(generatedDocument, "The call to GetDocumentState found a SourceGeneratedDocumentState, so we should have found it now.");
return generatedDocument;
}
else if (documentState is DocumentState)
{
return GetDocument(documentState.Id)!;
}
}
return null;
}
/// <summary>
/// Creates a new solution instance that includes a project with the specified language and names.
/// Returns the new project.
/// </summary>
public Project AddProject(string name, string assemblyName, string language)
{
var id = ProjectId.CreateNewId(debugName: name);
return this.AddProject(id, name, assemblyName, language).GetProject(id)!;
}
/// <summary>
/// Creates a new solution instance that includes a project with the specified language and names.
/// </summary>
public Solution AddProject(ProjectId projectId, string name, string assemblyName, string language)
=> this.AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), name, assemblyName, language));
/// <summary>
/// Create a new solution instance that includes a project with the specified project information.
/// </summary>
public Solution AddProject(ProjectInfo projectInfo)
{
var newState = _state.AddProject(projectInfo);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance without the project specified.
/// </summary>
public Solution RemoveProject(ProjectId projectId)
{
var newState = _state.RemoveProject(projectId);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the new
/// assembly name.
/// </summary>
public Solution WithProjectAssemblyName(ProjectId projectId, string assemblyName)
{
CheckContainsProject(projectId);
if (assemblyName == null)
{
throw new ArgumentNullException(nameof(assemblyName));
}
var newState = _state.WithProjectAssemblyName(projectId, assemblyName);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the output file path.
/// </summary>
public Solution WithProjectOutputFilePath(ProjectId projectId, string? outputFilePath)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectOutputFilePath(projectId, outputFilePath);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the reference assembly output file path.
/// </summary>
public Solution WithProjectOutputRefFilePath(ProjectId projectId, string? outputRefFilePath)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectOutputRefFilePath(projectId, outputRefFilePath);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the compiler output file path.
/// </summary>
public Solution WithProjectCompilationOutputInfo(ProjectId projectId, in CompilationOutputInfo info)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectCompilationOutputInfo(projectId, info);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the default namespace.
/// </summary>
public Solution WithProjectDefaultNamespace(ProjectId projectId, string? defaultNamespace)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectDefaultNamespace(projectId, defaultNamespace);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the specified attributes.
/// </summary>
internal Solution WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the name.
/// </summary>
public Solution WithProjectName(ProjectId projectId, string name)
{
CheckContainsProject(projectId);
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var newState = _state.WithProjectName(projectId, name);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the project file path.
/// </summary>
public Solution WithProjectFilePath(ProjectId projectId, string? filePath)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectFilePath(projectId, filePath);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified compilation options.
/// </summary>
public Solution WithProjectCompilationOptions(ProjectId projectId, CompilationOptions options)
{
CheckContainsProject(projectId);
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
var newState = _state.WithProjectCompilationOptions(projectId, options);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified parse options.
/// </summary>
public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions options)
{
CheckContainsProject(projectId);
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
var newState = _state.WithProjectParseOptions(projectId, options);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified hasAllInformation.
/// </summary>
// TODO: https://github.com/dotnet/roslyn/issues/42449 make it public
internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformation)
{
CheckContainsProject(projectId);
var newState = _state.WithHasAllInformation(projectId, hasAllInformation);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to have
/// the specified runAnalyzers.
/// </summary>
// TODO: https://github.com/dotnet/roslyn/issues/42449 make it public
internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers)
{
CheckContainsProject(projectId);
var newState = _state.WithRunAnalyzers(projectId, runAnalyzers);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project documents in the order by the specified document ids.
/// The specified document ids must be the same as what is already in the project; no adding or removing is allowed.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="documentIds"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="ArgumentException">The number of documents specified in <paramref name="documentIds"/> is not equal to the number of documents in project <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">Document specified in <paramref name="documentIds"/> does not exist in project <paramref name="projectId"/>.</exception>
public Solution WithProjectDocumentsOrder(ProjectId projectId, ImmutableList<DocumentId> documentIds)
{
CheckContainsProject(projectId);
if (documentIds == null)
{
throw new ArgumentNullException(nameof(documentIds));
}
var newState = _state.WithProjectDocumentsOrder(projectId, documentIds);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to include
/// the specified project reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="projectReference"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project already references the target project.</exception>
public Solution AddProjectReference(ProjectId projectId, ProjectReference projectReference)
{
return AddProjectReferences(projectId,
SpecializedCollections.SingletonEnumerable(
projectReference ?? throw new ArgumentNullException(nameof(projectReference))));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include
/// the specified project references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="projectReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="projectReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project already references the target project.</exception>
/// <exception cref="InvalidOperationException">Adding the project reference would create a circular dependency.</exception>
public Solution AddProjectReferences(ProjectId projectId, IEnumerable<ProjectReference> projectReferences)
{
CheckContainsProject(projectId);
// avoid enumerating multiple times:
var collection = projectReferences?.ToCollection();
PublicContract.RequireUniqueNonNullItems(collection, nameof(projectReferences));
foreach (var projectReference in collection)
{
if (_state.ContainsProjectReference(projectId, projectReference))
{
throw new InvalidOperationException(WorkspacesResources.The_project_already_references_the_target_project);
}
}
CheckCircularProjectReferences(projectId, collection);
CheckSubmissionProjectReferences(projectId, collection, ignoreExistingReferences: false);
var newState = _state.AddProjectReferences(projectId, collection);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to no longer
/// include the specified project reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="projectReference"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The solution does not contain <paramref name="projectId"/>.</exception>
public Solution RemoveProjectReference(ProjectId projectId, ProjectReference projectReference)
{
if (projectReference == null)
{
throw new ArgumentNullException(nameof(projectReference));
}
CheckContainsProject(projectId);
var newState = _state.RemoveProjectReference(projectId, projectReference);
if (newState == _state)
{
throw new ArgumentException(WorkspacesResources.Project_does_not_contain_specified_reference, nameof(projectReference));
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to contain
/// the specified list of project references.
/// </summary>
/// <param name="projectId">Id of the project whose references to replace with <paramref name="projectReferences"/>.</param>
/// <param name="projectReferences">New project references.</param>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="projectReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="projectReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
public Solution WithProjectReferences(ProjectId projectId, IEnumerable<ProjectReference>? projectReferences)
{
CheckContainsProject(projectId);
// avoid enumerating multiple times:
var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(projectReferences, nameof(projectReferences));
CheckCircularProjectReferences(projectId, collection);
CheckSubmissionProjectReferences(projectId, collection, ignoreExistingReferences: true);
var newState = _state.WithProjectReferences(projectId, collection);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified metadata reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="metadataReference"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
public Solution AddMetadataReference(ProjectId projectId, MetadataReference metadataReference)
{
return AddMetadataReferences(projectId,
SpecializedCollections.SingletonEnumerable(
metadataReference ?? throw new ArgumentNullException(nameof(metadataReference))));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified metadata references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="metadataReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="metadataReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
public Solution AddMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
{
CheckContainsProject(projectId);
// avoid enumerating multiple times:
var collection = metadataReferences?.ToCollection();
PublicContract.RequireUniqueNonNullItems(collection, nameof(metadataReferences));
foreach (var metadataReference in collection)
{
if (_state.ContainsMetadataReference(projectId, metadataReference))
{
throw new InvalidOperationException(WorkspacesResources.The_project_already_contains_the_specified_reference);
}
}
var newState = _state.AddMetadataReferences(projectId, collection);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to no longer include
/// the specified metadata reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="metadataReference"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project does not contain the specified reference.</exception>
public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference metadataReference)
{
CheckContainsProject(projectId);
if (metadataReference == null)
{
throw new ArgumentNullException(nameof(metadataReference));
}
var newState = _state.RemoveMetadataReference(projectId, metadataReference);
if (newState == _state)
{
throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference);
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to include only the
/// specified metadata references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="metadataReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="metadataReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable<MetadataReference> metadataReferences)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectMetadataReferences(
projectId,
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(metadataReferences, nameof(metadataReferences)));
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified analyzer reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
public Solution AddAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference)
{
return AddAnalyzerReferences(projectId,
SpecializedCollections.SingletonEnumerable(
analyzerReference ?? throw new ArgumentNullException(nameof(analyzerReference))));
}
/// <summary>
/// Create a new solution instance with the project specified updated to include the
/// specified analyzer references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project already contains the specified reference.</exception>
public Solution AddAnalyzerReferences(ProjectId projectId, IEnumerable<AnalyzerReference> analyzerReferences)
{
CheckContainsProject(projectId);
if (analyzerReferences is null)
{
throw new ArgumentNullException(nameof(analyzerReferences));
}
var collection = analyzerReferences.ToImmutableArray();
PublicContract.RequireUniqueNonNullItems(collection, nameof(analyzerReferences));
foreach (var analyzerReference in collection)
{
if (_state.ContainsAnalyzerReference(projectId, analyzerReference))
{
throw new InvalidOperationException(WorkspacesResources.The_project_already_contains_the_specified_reference);
}
}
var newState = _state.AddAnalyzerReferences(projectId, collection);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to no longer include
/// the specified analyzer reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
/// <exception cref="InvalidOperationException">The project does not contain the specified reference.</exception>
public Solution RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference)
{
CheckContainsProject(projectId);
if (analyzerReference == null)
{
throw new ArgumentNullException(nameof(analyzerReference));
}
var newState = _state.RemoveAnalyzerReference(projectId, analyzerReference);
if (newState == _state)
{
throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference);
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to include only the
/// specified analyzer references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="projectId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain <paramref name="projectId"/>.</exception>
public Solution WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable<AnalyzerReference> analyzerReferences)
{
CheckContainsProject(projectId);
var newState = _state.WithProjectAnalyzerReferences(
projectId,
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)));
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance updated to include the specified analyzer reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
public Solution AddAnalyzerReference(AnalyzerReference analyzerReference)
{
return AddAnalyzerReferences(
SpecializedCollections.SingletonEnumerable(
analyzerReference ?? throw new ArgumentNullException(nameof(analyzerReference))));
}
/// <summary>
/// Create a new solution instance updated to include the specified analyzer references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
/// <exception cref="InvalidOperationException">The solution already contains the specified reference.</exception>
public Solution AddAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
{
// avoid enumerating multiple times:
var collection = analyzerReferences?.ToCollection();
PublicContract.RequireUniqueNonNullItems(collection, nameof(analyzerReferences));
foreach (var analyzerReference in collection)
{
if (_state.AnalyzerReferences.Contains(analyzerReference))
{
throw new InvalidOperationException(WorkspacesResources.The_solution_already_contains_the_specified_reference);
}
}
var newState = _state.AddAnalyzerReferences(collection);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Create a new solution instance with the project specified updated to no longer include
/// the specified analyzer reference.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReference"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The solution does not contain the specified reference.</exception>
public Solution RemoveAnalyzerReference(AnalyzerReference analyzerReference)
{
if (analyzerReference == null)
{
throw new ArgumentNullException(nameof(analyzerReference));
}
var newState = _state.RemoveAnalyzerReference(analyzerReference);
if (newState == _state)
{
throw new InvalidOperationException(WorkspacesResources.Solution_does_not_contain_specified_reference);
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the specified analyzer references.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="analyzerReferences"/> contains <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="analyzerReferences"/> contains duplicate items.</exception>
public Solution WithAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
{
var newState = _state.WithAnalyzerReferences(
PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)));
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
private static SourceCodeKind GetSourceCodeKind(ProjectState project)
=> project.ParseOptions != null ? project.ParseOptions.Kind : SourceCodeKind.Regular;
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// document instance defined by its name and text.
/// </summary>
public Solution AddDocument(DocumentId documentId, string name, string text, IEnumerable<string>? folders = null, string? filePath = null)
{
if (documentId == null)
throw new ArgumentNullException(nameof(documentId));
if (name == null)
throw new ArgumentNullException(nameof(name));
var project = GetRequiredProjectState(documentId.ProjectId);
var sourceText = SourceText.From(text, encoding: null, checksumAlgorithm: project.ChecksumAlgorithm);
return AddDocumentImpl(project, documentId, name, sourceText, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated: false);
}
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// document instance defined by its name and text.
/// </summary>
public Solution AddDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null, bool isGenerated = false)
{
if (documentId == null)
throw new ArgumentNullException(nameof(documentId));
if (name == null)
throw new ArgumentNullException(nameof(name));
if (text == null)
throw new ArgumentNullException(nameof(text));
var project = GetRequiredProjectState(documentId.ProjectId);
return AddDocumentImpl(project, documentId, name, text, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated);
}
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// document instance defined by its name and root <see cref="SyntaxNode"/>.
/// </summary>
public Solution AddDocument(DocumentId documentId, string name, SyntaxNode syntaxRoot, IEnumerable<string>? folders = null, string? filePath = null, bool isGenerated = false, PreservationMode preservationMode = PreservationMode.PreserveValue)
{
if (documentId == null)
throw new ArgumentNullException(nameof(documentId));
if (name == null)
throw new ArgumentNullException(nameof(name));
if (syntaxRoot == null)
throw new ArgumentNullException(nameof(syntaxRoot));
var project = GetRequiredProjectState(documentId.ProjectId);
var sourceText = SourceText.From(string.Empty, encoding: null, project.ChecksumAlgorithm);
return AddDocumentImpl(project, documentId, name, sourceText, PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)), filePath, isGenerated).
WithDocumentSyntaxRoot(documentId, syntaxRoot, preservationMode);
}
private Solution AddDocumentImpl(ProjectState project, DocumentId documentId, string name, SourceText text, IReadOnlyList<string>? folders, string? filePath, bool isGenerated)
=> AddDocument(DocumentInfo.Create(
documentId,
name: name,
folders: folders,
sourceCodeKind: GetSourceCodeKind(project),
loader: TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), name)),
filePath: filePath,
isGenerated: isGenerated));
/// <summary>
/// Creates a new solution instance with the project updated to include a new document with
/// the arguments specified.
/// </summary>
public Solution AddDocument(DocumentId documentId, string name, TextLoader loader, IEnumerable<string>? folders = null)
{
if (documentId == null)
throw new ArgumentNullException(nameof(documentId));
if (name == null)
throw new ArgumentNullException(nameof(name));
if (loader == null)
throw new ArgumentNullException(nameof(loader));
var project = GetRequiredProjectState(documentId.ProjectId);
return AddDocument(DocumentInfo.Create(
documentId,
name,
folders,
GetSourceCodeKind(project),
loader));
}
/// <summary>
/// Create a new solution instance with the corresponding project updated to include a new
/// document instanced defined by the document info.
/// </summary>
public Solution AddDocument(DocumentInfo documentInfo)
=> AddDocuments(ImmutableArray.Create(documentInfo));
/// <summary>
/// Create a new <see cref="Solution"/> instance with the corresponding <see cref="Project"/>s updated to include
/// the documents specified by <paramref name="documentInfos"/>.
/// </summary>
/// <returns>A new <see cref="Solution"/> with the documents added.</returns>
public Solution AddDocuments(ImmutableArray<DocumentInfo> documentInfos)
{
var newState = _state.AddDocuments(documentInfos);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// additional document instance defined by its name and text.
/// </summary>
public Solution AddAdditionalDocument(DocumentId documentId, string name, string text, IEnumerable<string>? folders = null, string? filePath = null)
=> this.AddAdditionalDocument(documentId, name, SourceText.From(text), folders, filePath);
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// additional document instance defined by its name and text.
/// </summary>
public Solution AddAdditionalDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null)
{
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var info = CreateDocumentInfo(documentId, name, text, folders, filePath);
return this.AddAdditionalDocument(info);
}
public Solution AddAdditionalDocument(DocumentInfo documentInfo)
=> AddAdditionalDocuments(ImmutableArray.Create(documentInfo));
public Solution AddAdditionalDocuments(ImmutableArray<DocumentInfo> documentInfos)
{
var newState = _state.AddAdditionalDocuments(documentInfos);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the corresponding project updated to include a new
/// analyzer config document instance defined by its name and text.
/// </summary>
public Solution AddAnalyzerConfigDocument(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders = null, string? filePath = null)
{
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
// TODO: should we validate file path?
// https://github.com/dotnet/roslyn/issues/41940
var info = CreateDocumentInfo(documentId, name, text, folders, filePath);
return this.AddAnalyzerConfigDocuments(ImmutableArray.Create(info));
}
private DocumentInfo CreateDocumentInfo(DocumentId documentId, string name, SourceText text, IEnumerable<string>? folders, string? filePath)
{
var project = GetRequiredProjectState(documentId.ProjectId);
var version = VersionStamp.Create();
var loader = TextLoader.From(TextAndVersion.Create(text, version, name));
return DocumentInfo.Create(
documentId,
name: name,
folders: folders,
sourceCodeKind: GetSourceCodeKind(project),
loader: loader,
filePath: filePath);
}
private ProjectState GetRequiredProjectState(ProjectId projectId)
=> _state.GetProjectState(projectId) ?? throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, projectId));
/// <summary>
/// Creates a new Solution instance that contains a new compiler configuration document like a .editorconfig file.
/// </summary>
public Solution AddAnalyzerConfigDocuments(ImmutableArray<DocumentInfo> documentInfos)
{
var newState = _state.AddAnalyzerConfigDocuments(documentInfos);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified document.
/// </summary>
public Solution RemoveDocument(DocumentId documentId)
{
CheckContainsDocument(documentId);
return RemoveDocumentsImpl(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified documents.
/// </summary>
public Solution RemoveDocuments(ImmutableArray<DocumentId> documentIds)
{
CheckContainsDocuments(documentIds);
return RemoveDocumentsImpl(documentIds);
}
private Solution RemoveDocumentsImpl(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveDocuments(documentIds);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified additional document.
/// </summary>
public Solution RemoveAdditionalDocument(DocumentId documentId)
{
CheckContainsAdditionalDocument(documentId);
return RemoveAdditionalDocumentsImpl(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified additional documents.
/// </summary>
public Solution RemoveAdditionalDocuments(ImmutableArray<DocumentId> documentIds)
{
CheckContainsAdditionalDocuments(documentIds);
return RemoveAdditionalDocumentsImpl(documentIds);
}
private Solution RemoveAdditionalDocumentsImpl(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveAdditionalDocuments(documentIds);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>.
/// </summary>
public Solution RemoveAnalyzerConfigDocument(DocumentId documentId)
{
CheckContainsAnalyzerConfigDocument(documentId);
return RemoveAnalyzerConfigDocumentsImpl(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>s.
/// </summary>
public Solution RemoveAnalyzerConfigDocuments(ImmutableArray<DocumentId> documentIds)
{
CheckContainsAnalyzerConfigDocuments(documentIds);
return RemoveAnalyzerConfigDocumentsImpl(documentIds);
}
private Solution RemoveAnalyzerConfigDocumentsImpl(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveAnalyzerConfigDocuments(documentIds);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the new name.
/// </summary>
public Solution WithDocumentName(DocumentId documentId, string name)
{
CheckContainsDocument(documentId);
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var newState = _state.WithDocumentName(documentId, name);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to be contained in
/// the sequence of logical folders.
/// </summary>
public Solution WithDocumentFolders(DocumentId documentId, IEnumerable<string>? folders)
{
CheckContainsDocument(documentId);
var newState = _state.WithDocumentFolders(documentId,
PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)));
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the specified file path.
/// </summary>
public Solution WithDocumentFilePath(DocumentId documentId, string filePath)
{
CheckContainsDocument(documentId);
// TODO (https://github.com/dotnet/roslyn/issues/37125):
// We *do* support null file paths. Why can't you switch a document back to null?
// See DocumentState.GetSyntaxTreeFilePath
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
var newState = _state.WithDocumentFilePath(documentId, filePath);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the text
/// specified.
/// </summary>
public Solution WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsDocument(documentId);
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithDocumentText(documentId, text, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the additional document specified updated to have the text
/// specified.
/// </summary>
public Solution WithAdditionalDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsAdditionalDocument(documentId);
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithAdditionalDocumentText(documentId, text, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the analyzer config document specified updated to have the text
/// supplied by the text loader.
/// </summary>
public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsAnalyzerConfigDocument(documentId);
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithAnalyzerConfigDocumentText(documentId, text, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the text
/// and version specified.
/// </summary>
public Solution WithDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsDocument(documentId);
if (textAndVersion == null)
{
throw new ArgumentNullException(nameof(textAndVersion));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithDocumentText(documentId, textAndVersion, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the additional document specified updated to have the text
/// and version specified.
/// </summary>
public Solution WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsAdditionalDocument(documentId);
if (textAndVersion == null)
{
throw new ArgumentNullException(nameof(textAndVersion));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithAdditionalDocumentText(documentId, textAndVersion, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the analyzer config document specified updated to have the text
/// and version specified.
/// </summary>
public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsAnalyzerConfigDocument(documentId);
if (textAndVersion == null)
{
throw new ArgumentNullException(nameof(textAndVersion));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have a syntax tree
/// rooted by the specified syntax node.
/// </summary>
public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue)
{
CheckContainsDocument(documentId);
if (root == null)
{
throw new ArgumentNullException(nameof(root));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithDocumentSyntaxRoot(documentId, root, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState)
{
var newState = _state.WithDocumentContentsFrom(documentId, documentState);
return newState == _state ? this : new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the source
/// code kind specified.
/// </summary>
public Solution WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind sourceCodeKind)
{
CheckContainsDocument(documentId);
#pragma warning disable CS0618 // Interactive is obsolete but this method accepts it for backward compatibility.
if (sourceCodeKind == SourceCodeKind.Interactive)
{
sourceCodeKind = SourceCodeKind.Script;
}
#pragma warning restore CS0618
if (!sourceCodeKind.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(sourceCodeKind));
}
var newState = _state.WithDocumentSourceCodeKind(documentId, sourceCodeKind);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the document specified updated to have the text
/// supplied by the text loader.
/// </summary>
public Solution WithDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
{
CheckContainsDocument(documentId);
if (loader == null)
{
throw new ArgumentNullException(nameof(loader));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.UpdateDocumentTextLoader(documentId, loader, mode);
// Note: state is currently not reused.
// If UpdateDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal.
Debug.Assert(newState != _state);
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the additional document specified updated to have the text
/// supplied by the text loader.
/// </summary>
public Solution WithAdditionalDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
{
CheckContainsAdditionalDocument(documentId);
if (loader == null)
{
throw new ArgumentNullException(nameof(loader));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.UpdateAdditionalDocumentTextLoader(documentId, loader, mode);
// Note: state is currently not reused.
// If UpdateAdditionalDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal.
Debug.Assert(newState != _state);
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the analyzer config document specified updated to have the text
/// supplied by the text loader.
/// </summary>
public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode)
{
CheckContainsAnalyzerConfigDocument(documentId);
if (loader == null)
{
throw new ArgumentNullException(nameof(loader));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode);
// Note: state is currently not reused.
// If UpdateAnalyzerConfigDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal.
Debug.Assert(newState != _state);
return new Solution(newState);
}
/// <summary>
/// Creates a branch of the solution that has its compilations frozen in whatever state they are in at the time, assuming a background compiler is
/// busy building this compilations.
///
/// A compilation for the project containing the specified document id will be guaranteed to exist with at least the syntax tree for the document.
///
/// This not intended to be the public API, use Document.WithFrozenPartialSemantics() instead.
/// </summary>
internal Solution WithFrozenPartialCompilationIncludingSpecificDocument(DocumentId documentId, CancellationToken cancellationToken)
{
var newState = _state.WithFrozenPartialCompilationIncludingSpecificDocument(documentId, cancellationToken);
return new Solution(newState);
}
internal async Task<Solution> WithMergedLinkedFileChangesAsync(
Solution oldSolution,
SolutionChanges? solutionChanges = null,
IMergeConflictHandler? mergeConflictHandler = null,
CancellationToken cancellationToken = default)
{
// we only log sessioninfo for actual changes committed to workspace which should exclude ones from preview
var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution));
return (await session.MergeDiffsAsync(mergeConflictHandler, cancellationToken).ConfigureAwait(false)).MergedSolution;
}
internal ImmutableArray<DocumentId> GetRelatedDocumentIds(DocumentId documentId)
{
return _state.GetRelatedDocumentIds(documentId);
}
internal Solution WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services)
{
var newState = _state.WithNewWorkspace(workspaceKind, workspaceVersion, services);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Formerly, returned a copy of the solution isolated from the original so that they do not share computed state. It now does nothing.
/// </summary>
[Obsolete("This method no longer produces a Solution that does not share state and is no longer necessary to call.", error: false)]
[EditorBrowsable(EditorBrowsableState.Never)] // hide this since it is obsolete and only leads to confusion
public Solution GetIsolatedSolution()
{
// To maintain compat, just return ourself, which will be functionally identical.
return this;
}
/// <summary>
/// Creates a new solution instance with all the documents specified updated to have the same specified text.
/// </summary>
public Solution WithDocumentText(IEnumerable<DocumentId?> documentIds, SourceText text, PreservationMode mode = PreservationMode.PreserveValue)
{
if (documentIds == null)
{
throw new ArgumentNullException(nameof(documentIds));
}
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
if (!mode.IsValid())
{
throw new ArgumentOutOfRangeException(nameof(mode));
}
var newState = _state.WithDocumentText(documentIds, text, mode);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Returns a new Solution that will always produce a specific output for a generated file. This is used only in the
/// implementation of <see cref="TextExtensions.GetOpenDocumentInCurrentContextWithChanges"/> where if a user has a source
/// generated file open, we need to make sure everything lines up.
/// </summary>
internal Document WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdentity documentIdentity, SourceText text)
{
var newState = _state.WithFrozenSourceGeneratedDocument(documentIdentity, text);
var newSolution = newState != _state ? new Solution(newState) : this;
var newDocumentState = newState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId);
Contract.ThrowIfNull(newDocumentState, "Because we just froze this document, it should always exist.");
var newProject = newSolution.GetRequiredProject(newDocumentState.Id.ProjectId);
return newProject.GetOrCreateSourceGeneratedDocument(newDocumentState);
}
/// <summary>
/// Undoes the operation of <see cref="WithFrozenSourceGeneratedDocument"/>; any frozen source generated document is allowed
/// to have it's real output again.
/// </summary>
internal Solution WithoutFrozenSourceGeneratedDocuments()
{
var newState = _state.WithoutFrozenSourceGeneratedDocuments();
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Returns a new Solution which represents the same state as before, but with the cached generator driver state from the given project updated to match.
/// </summary>
/// <remarks>
/// When generators are ran in a Solution snapshot, they may cache state to speed up future runs. For Razor, we only run their generator on forked
/// solutions that are thrown away; this API gives us a way to reuse that cached state in other forked solutions, since otherwise there's no way to reuse
/// the cached state.
/// </remarks>
internal Solution WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState)
{
var newState = _state.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Gets an objects that lists the added, changed and removed projects between
/// this solution and the specified solution.
/// </summary>
public SolutionChanges GetChanges(Solution oldSolution)
{
if (oldSolution == null)
{
throw new ArgumentNullException(nameof(oldSolution));
}
return new SolutionChanges(this, oldSolution);
}
/// <summary>
/// Gets the set of <see cref="DocumentId"/>s in this <see cref="Solution"/> with a
/// <see cref="TextDocument.FilePath"/> that matches the given file path.
/// </summary>
public ImmutableArray<DocumentId> GetDocumentIdsWithFilePath(string? filePath) => _state.GetDocumentIdsWithFilePath(filePath);
/// <summary>
/// Gets a <see cref="ProjectDependencyGraph"/> that details the dependencies between projects for this solution.
/// </summary>
public ProjectDependencyGraph GetProjectDependencyGraph() => _state.GetProjectDependencyGraph();
/// <summary>
/// Returns the options that should be applied to this solution. This is equivalent to <see cref="Workspace.Options" /> when the <see cref="Solution"/>
/// instance was created.
/// </summary>
public OptionSet Options => _state.Options;
/// <summary>
/// Analyzer references associated with the solution.
/// </summary>
public IReadOnlyList<AnalyzerReference> AnalyzerReferences => _state.AnalyzerReferences;
/// <summary>
/// Creates a new solution instance with the specified <paramref name="options"/>.
/// </summary>
public Solution WithOptions(OptionSet options)
{
return options switch
{
SolutionOptionSet serializableOptions => WithOptions(serializableOptions),
null => throw new ArgumentNullException(nameof(options)),
_ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(options))
};
}
/// <summary>
/// Creates a new solution instance with the specified serializable <paramref name="options"/>.
/// </summary>
internal Solution WithOptions(SolutionOptionSet options)
{
var newState = _state.WithOptions(options: options);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
private void CheckContainsProject(ProjectId projectId)
{
if (projectId == null)
{
throw new ArgumentNullException(nameof(projectId));
}
if (!ContainsProject(projectId))
{
throw new InvalidOperationException(WorkspacesResources.The_solution_does_not_contain_the_specified_project);
}
}
private void CheckContainsDocument(DocumentId documentId)
{
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
if (!ContainsDocument(documentId))
{
throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
}
}
private void CheckContainsDocuments(ImmutableArray<DocumentId> documentIds)
{
if (documentIds.IsDefault)
{
throw new ArgumentNullException(nameof(documentIds));
}
foreach (var documentId in documentIds)
{
CheckContainsDocument(documentId);
}
}
private void CheckContainsAdditionalDocument(DocumentId documentId)
{
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
if (!ContainsAdditionalDocument(documentId))
{
throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
}
}
private void CheckContainsAdditionalDocuments(ImmutableArray<DocumentId> documentIds)
{
if (documentIds.IsDefault)
{
throw new ArgumentNullException(nameof(documentIds));
}
foreach (var documentId in documentIds)
{
CheckContainsAdditionalDocument(documentId);
}
}
private void CheckContainsAnalyzerConfigDocument(DocumentId documentId)
{
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
if (!ContainsAnalyzerConfigDocument(documentId))
{
throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document);
}
}
private void CheckContainsAnalyzerConfigDocuments(ImmutableArray<DocumentId> documentIds)
{
if (documentIds.IsDefault)
{
throw new ArgumentNullException(nameof(documentIds));
}
foreach (var documentId in documentIds)
{
CheckContainsAnalyzerConfigDocument(documentId);
}
}
/// <summary>
/// Throws if setting the project references of project <paramref name="projectId"/> to specified <paramref name="projectReferences"/>
/// would form a cycle in project dependency graph.
/// </summary>
private void CheckCircularProjectReferences(ProjectId projectId, IReadOnlyCollection<ProjectReference> projectReferences)
{
foreach (var projectReference in projectReferences)
{
if (projectId == projectReference.ProjectId)
{
throw new InvalidOperationException(WorkspacesResources.A_project_may_not_reference_itself);
}
if (_state.ContainsTransitiveReference(projectReference.ProjectId, projectId))
{
throw new InvalidOperationException(
string.Format(WorkspacesResources.Adding_project_reference_from_0_to_1_will_cause_a_circular_reference,
projectId,
projectReference.ProjectId));
}
}
}
/// <summary>
/// Throws if setting the project references of project <paramref name="projectId"/> to specified <paramref name="projectReferences"/>
/// would form an invalid submission project chain.
///
/// Submission projects can reference at most one other submission project. Regular projects can't reference any.
/// </summary>
private void CheckSubmissionProjectReferences(ProjectId projectId, IEnumerable<ProjectReference> projectReferences, bool ignoreExistingReferences)
{
var projectState = _state.GetRequiredProjectState(projectId);
var isSubmission = projectState.IsSubmission;
var hasSubmissionReference = !ignoreExistingReferences && projectState.ProjectReferences.Any(p => _state.GetRequiredProjectState(p.ProjectId).IsSubmission);
foreach (var projectReference in projectReferences)
{
// Note: need to handle reference to a project that's not included in the solution:
var referencedProjectState = _state.GetProjectState(projectReference.ProjectId);
if (referencedProjectState != null && referencedProjectState.IsSubmission)
{
if (!isSubmission)
{
throw new InvalidOperationException(WorkspacesResources.Only_submission_project_can_reference_submission_projects);
}
if (hasSubmissionReference)
{
throw new InvalidOperationException(WorkspacesResources.This_submission_already_references_another_submission_project);
}
hasSubmissionReference = true;
}
}
}
}
}
|