ProjectState.cs 43.2 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
P
Pilchie 已提交
2

3 4
#nullable enable

P
Pilchie 已提交
5 6 7 8
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
9
using System.Diagnostics.CodeAnalysis;
P
Pilchie 已提交
10
using System.Linq;
11
using System.Runtime.CompilerServices;
P
Pilchie 已提交
12 13 14
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
15
using Microsoft.CodeAnalysis.Diagnostics;
16
using Microsoft.CodeAnalysis.Host;
17
using Microsoft.CodeAnalysis.Serialization;
P
Pilchie 已提交
18 19 20 21 22 23
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
    internal partial class ProjectState
    {
24 25 26
        private readonly ProjectInfo _projectInfo;
        private readonly HostLanguageServices _languageServices;
        private readonly SolutionServices _solutionServices;
27 28 29 30 31 32 33 34 35 36 37 38 39

        /// <summary>
        /// The documents in this project. They are sorted by <see cref="DocumentId.Id"/> to provide a stable sort for
        /// <see cref="GetChecksumAsync(CancellationToken)"/>.
        /// </summary>
        private readonly ImmutableSortedDictionary<DocumentId, DocumentState> _documentStates;

        /// <summary>
        /// The additional documents in this project. They are sorted by <see cref="DocumentId.Id"/> to provide a stable sort for
        /// <see cref="GetChecksumAsync(CancellationToken)"/>.
        /// </summary>
        private readonly ImmutableSortedDictionary<DocumentId, TextDocumentState> _additionalDocumentStates;

40 41 42 43 44 45
        /// <summary>
        /// The analyzer config documents in this project.  They are sorted by <see cref="DocumentId.Id"/> to provide a stable sort for
        /// <see cref="GetChecksumAsync(CancellationToken)"/>.
        /// </summary>
        private readonly ImmutableSortedDictionary<DocumentId, AnalyzerConfigDocumentState> _analyzerConfigDocumentStates;

46 47
        private readonly ImmutableList<DocumentId> _documentIds;
        private readonly ImmutableList<DocumentId> _additionalDocumentIds;
48 49
        private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentVersion;
        private readonly AsyncLazy<VersionStamp> _lazyLatestDocumentTopLevelChangeVersion;
50

51 52 53
        // Checksums for this solution state
        private readonly ValueSource<ProjectStateChecksums> _lazyChecksums;

54 55 56 57 58
        /// <summary>
        /// The <see cref="AnalyzerConfigSet"/> to be used for analyzer options for specific trees.
        /// </summary>
        private readonly ValueSource<AnalyzerConfigSet> _lazyAnalyzerConfigSet;

59
        // this will be initialized lazily.
60
        private AnalyzerOptions? _analyzerOptionsDoNotAccessDirectly;
P
Pilchie 已提交
61 62 63

        private ProjectState(
            ProjectInfo projectInfo,
64
            HostLanguageServices languageServices,
P
Pilchie 已提交
65
            SolutionServices solutionServices,
66 67
            ImmutableList<DocumentId> documentIds,
            ImmutableList<DocumentId> additionalDocumentIds,
68 69
            ImmutableSortedDictionary<DocumentId, DocumentState> documentStates,
            ImmutableSortedDictionary<DocumentId, TextDocumentState> additionalDocumentStates,
70
            ImmutableSortedDictionary<DocumentId, AnalyzerConfigDocumentState> analyzerConfigDocumentStates,
P
Pilchie 已提交
71
            AsyncLazy<VersionStamp> lazyLatestDocumentVersion,
72 73
            AsyncLazy<VersionStamp> lazyLatestDocumentTopLevelChangeVersion,
            ValueSource<AnalyzerConfigSet> lazyAnalyzerConfigSet)
P
Pilchie 已提交
74
        {
75 76
            _solutionServices = solutionServices;
            _languageServices = languageServices;
77 78
            _documentIds = documentIds;
            _additionalDocumentIds = additionalDocumentIds;
79 80
            _documentStates = documentStates;
            _additionalDocumentStates = additionalDocumentStates;
81
            _analyzerConfigDocumentStates = analyzerConfigDocumentStates;
82 83
            _lazyLatestDocumentVersion = lazyLatestDocumentVersion;
            _lazyLatestDocumentTopLevelChangeVersion = lazyLatestDocumentTopLevelChangeVersion;
84
            _lazyAnalyzerConfigSet = lazyAnalyzerConfigSet;
85

86 87 88 89 90
            // ownership of information on document has moved to project state. clear out documentInfo the state is
            // holding on. otherwise, these information will be held onto unnecesarily by projectInfo even after
            // the info has changed by DocumentState.
            _projectInfo = ClearAllDocumentsFromProjectInfo(projectInfo);

91
            _lazyChecksums = new AsyncLazy<ProjectStateChecksums>(ComputeChecksumsAsync, cacheResult: true);
P
Pilchie 已提交
92 93
        }

94
        public ProjectState(ProjectInfo projectInfo, HostLanguageServices languageServices, SolutionServices solutionServices)
P
Pilchie 已提交
95 96
        {
            Contract.ThrowIfNull(projectInfo);
97
            Contract.ThrowIfNull(languageServices);
P
Pilchie 已提交
98 99
            Contract.ThrowIfNull(solutionServices);

100 101
            _languageServices = languageServices;
            _solutionServices = solutionServices;
P
Pilchie 已提交
102

103
            var projectInfoFixed = FixProjectInfo(projectInfo);
P
Pilchie 已提交
104

105
            // We need to compute our AnalyerConfigDocumentStates first, since we use those to produce our DocumentStates
106 107 108
            _analyzerConfigDocumentStates = ImmutableSortedDictionary.CreateRange(DocumentIdComparer.Instance,
                projectInfoFixed.AnalyzerConfigDocuments.Select(d =>
                    KeyValuePairUtil.Create(d.Id, new AnalyzerConfigDocumentState(d, solutionServices))));
109 110
            _lazyAnalyzerConfigSet = ComputeAnalyzerConfigSetValueSource(_analyzerConfigDocumentStates.Values);

111 112
            _documentIds = projectInfoFixed.Documents.Select(d => d.Id).ToImmutableList();
            _additionalDocumentIds = projectInfoFixed.AdditionalDocuments.Select(d => d.Id).ToImmutableList();
P
Pilchie 已提交
113

114
            var parseOptions = projectInfoFixed.ParseOptions;
115
            var docStates = ImmutableSortedDictionary.CreateRange(DocumentIdComparer.Instance,
116
                projectInfoFixed.Documents.Select(d =>
P
Pilchie 已提交
117
                    new KeyValuePair<DocumentId, DocumentState>(d.Id,
118
                        CreateDocument(d, parseOptions))));
P
Pilchie 已提交
119

120
            _documentStates = docStates;
P
Pilchie 已提交
121

122
            var additionalDocStates = ImmutableSortedDictionary.CreateRange(DocumentIdComparer.Instance,
123
                    projectInfoFixed.AdditionalDocuments.Select(d =>
124
                        new KeyValuePair<DocumentId, TextDocumentState>(d.Id, new TextDocumentState(d, solutionServices))));
125

126 127 128
            _additionalDocumentStates = additionalDocStates;
            _lazyLatestDocumentVersion = new AsyncLazy<VersionStamp>(c => ComputeLatestDocumentVersionAsync(docStates, additionalDocStates, c), cacheResult: true);
            _lazyLatestDocumentTopLevelChangeVersion = new AsyncLazy<VersionStamp>(c => ComputeLatestDocumentTopLevelChangeVersionAsync(docStates, additionalDocStates, c), cacheResult: true);
129

130 131 132 133 134 135
            // ownership of information on document has moved to project state. clear out documentInfo the state is
            // holding on. otherwise, these information will be held onto unnecesarily by projectInfo even after
            // the info has changed by DocumentState.
            // we hold onto the info so that we don't need to duplicate all information info already has in the state
            _projectInfo = ClearAllDocumentsFromProjectInfo(projectInfoFixed);

136
            _lazyChecksums = new AsyncLazy<ProjectStateChecksums>(ComputeChecksumsAsync, cacheResult: true);
P
Pilchie 已提交
137 138
        }

139 140
        private static ProjectInfo ClearAllDocumentsFromProjectInfo(ProjectInfo projectInfo)
        {
141 142 143 144
            return projectInfo
                .WithDocuments(ImmutableArray<DocumentInfo>.Empty)
                .WithAdditionalDocuments(ImmutableArray<DocumentInfo>.Empty)
                .WithAnalyzerConfigDocuments(ImmutableArray<DocumentInfo>.Empty);
145 146
        }

P
Pilchie 已提交
147 148 149 150
        private ProjectInfo FixProjectInfo(ProjectInfo projectInfo)
        {
            if (projectInfo.CompilationOptions == null)
            {
151
                var compilationFactory = _languageServices.GetService<ICompilationFactoryService>();
152 153 154 155
                if (compilationFactory != null)
                {
                    projectInfo = projectInfo.WithCompilationOptions(compilationFactory.GetDefaultCompilationOptions());
                }
P
Pilchie 已提交
156 157 158 159
            }

            if (projectInfo.ParseOptions == null)
            {
160
                var syntaxTreeFactory = _languageServices.GetService<ISyntaxTreeFactoryService>();
161 162 163 164
                if (syntaxTreeFactory != null)
                {
                    projectInfo = projectInfo.WithParseOptions(syntaxTreeFactory.GetDefaultParseOptions());
                }
P
Pilchie 已提交
165 166 167 168 169
            }

            return projectInfo;
        }

170
        private static async Task<VersionStamp> ComputeLatestDocumentVersionAsync(IImmutableDictionary<DocumentId, DocumentState> documentStates, IImmutableDictionary<DocumentId, TextDocumentState> additionalDocumentStates, CancellationToken cancellationToken)
P
Pilchie 已提交
171 172 173
        {
            // this may produce a version that is out of sync with the actual Document versions.
            var latestVersion = VersionStamp.Default;
174
            foreach (var (_, doc) in documentStates)
P
Pilchie 已提交
175 176 177 178 179 180 181 182 183 184
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (!doc.IsGenerated)
                {
                    var version = await doc.GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
                    latestVersion = version.GetNewerVersion(latestVersion);
                }
            }

185
            foreach (var (_, additionalDoc) in additionalDocumentStates)
186 187 188 189 190 191 192
            {
                cancellationToken.ThrowIfCancellationRequested();

                var version = await additionalDoc.GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
                latestVersion = version.GetNewerVersion(latestVersion);
            }

P
Pilchie 已提交
193 194 195
            return latestVersion;
        }

196
        private AsyncLazy<VersionStamp> CreateLazyLatestDocumentTopLevelChangeVersion(
197
            TextDocumentState newDocument,
198 199
            IImmutableDictionary<DocumentId, DocumentState> newDocumentStates,
            IImmutableDictionary<DocumentId, TextDocumentState> newAdditionalDocumentStates)
P
Pilchie 已提交
200
        {
C
CyrusNajmabadi 已提交
201
            if (_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var oldVersion))
P
Pilchie 已提交
202 203 204 205 206
            {
                return new AsyncLazy<VersionStamp>(c => ComputeTopLevelChangeTextVersionAsync(oldVersion, newDocument, c), cacheResult: true);
            }
            else
            {
207
                return new AsyncLazy<VersionStamp>(c => ComputeLatestDocumentTopLevelChangeVersionAsync(newDocumentStates, newAdditionalDocumentStates, c), cacheResult: true);
P
Pilchie 已提交
208 209 210
            }
        }

211
        private static async Task<VersionStamp> ComputeTopLevelChangeTextVersionAsync(VersionStamp oldVersion, TextDocumentState newDocument, CancellationToken cancellationToken)
P
Pilchie 已提交
212 213 214 215 216
        {
            var newVersion = await newDocument.GetTopLevelChangeTextVersionAsync(cancellationToken).ConfigureAwait(false);
            return newVersion.GetNewerVersion(oldVersion);
        }

217
        private static async Task<VersionStamp> ComputeLatestDocumentTopLevelChangeVersionAsync(IImmutableDictionary<DocumentId, DocumentState> documentStates, IImmutableDictionary<DocumentId, TextDocumentState> additionalDocumentStates, CancellationToken cancellationToken)
P
Pilchie 已提交
218 219 220
        {
            // this may produce a version that is out of sync with the actual Document versions.
            var latestVersion = VersionStamp.Default;
221
            foreach (var (_, doc) in documentStates)
P
Pilchie 已提交
222 223 224 225 226 227 228
            {
                cancellationToken.ThrowIfCancellationRequested();

                var version = await doc.GetTopLevelChangeTextVersionAsync(cancellationToken).ConfigureAwait(false);
                latestVersion = version.GetNewerVersion(latestVersion);
            }

229
            foreach (var (_, additionalDoc) in additionalDocumentStates)
230 231 232 233 234 235 236
            {
                cancellationToken.ThrowIfCancellationRequested();

                var version = await additionalDoc.GetTopLevelChangeTextVersionAsync(cancellationToken).ConfigureAwait(false);
                latestVersion = version.GetNewerVersion(latestVersion);
            }

P
Pilchie 已提交
237 238 239
            return latestVersion;
        }

240
        internal DocumentState CreateDocument(DocumentInfo documentInfo, ParseOptions? parseOptions)
P
Pilchie 已提交
241
        {
242
            var doc = new DocumentState(documentInfo, parseOptions, _lazyAnalyzerConfigSet, _languageServices, _solutionServices);
P
Pilchie 已提交
243 244 245 246 247 248 249 250

            if (doc.SourceCodeKind != documentInfo.SourceCodeKind)
            {
                doc = doc.UpdateSourceCodeKind(documentInfo.SourceCodeKind);
            }

            return doc;
        }
251

252 253 254 255
        public AnalyzerOptions AnalyzerOptions
        {
            get
            {
256
                if (_analyzerOptionsDoNotAccessDirectly == null)
257
                {
258
                    _analyzerOptionsDoNotAccessDirectly = new AnalyzerOptions(
259
                        _additionalDocumentStates.Values.Select(d => new AdditionalTextWithState(d)).ToImmutableArray<AdditionalText>(),
260
                        new WorkspaceAnalyzerConfigOptionsProvider(this));
261 262
                }

263
                return _analyzerOptionsDoNotAccessDirectly;
264 265 266
            }
        }

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        public ImmutableDictionary<string, ReportDiagnostic> GetAnalyzerConfigSpecialDiagnosticOptions()
        {
            // We need to find the analyzer config options at the root of the project.
            // Currently, there is no compiler API to query analyzer config options for a directory in a language agnostic fashion.
            // So, we use a dummy language-specific file name appended to the project directory to query analyzer config options.

            var projectDirectory = PathUtilities.GetDirectoryName(_projectInfo.FilePath);
            if (!PathUtilities.IsAbsolute(projectDirectory))
            {
                return ImmutableDictionary<string, ReportDiagnostic>.Empty;
            }

            var fileName = Guid.NewGuid().ToString();
            string sourceFilePath;
            switch (_projectInfo.Language)
            {
                case LanguageNames.CSharp:
                    sourceFilePath = PathUtilities.CombineAbsoluteAndRelativePaths(projectDirectory, $"{fileName}.cs");
                    break;

                case LanguageNames.VisualBasic:
                    sourceFilePath = PathUtilities.CombineAbsoluteAndRelativePaths(projectDirectory, $"{fileName}.vb");
                    break;

                default:
                    return ImmutableDictionary<string, ReportDiagnostic>.Empty;
            }

            return _lazyAnalyzerConfigSet.GetValue(CancellationToken.None).GetOptionsForSourcePath(sourceFilePath).TreeOptions;
        }

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
        private sealed class WorkspaceAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider
        {
            private readonly ProjectState _projectState;

            public WorkspaceAnalyzerConfigOptionsProvider(ProjectState projectState)
            {
                _projectState = projectState;
            }

            public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
            {
                return new WorkspaceAnalyzerConfigOptions(_projectState._lazyAnalyzerConfigSet.GetValue(CancellationToken.None).GetOptionsForSourcePath(tree.FilePath));
            }

            public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
            {
                // TODO: correctly find the file path, since it looks like we give this the document's .Name under the covers if we don't have one
                return new WorkspaceAnalyzerConfigOptions(_projectState._lazyAnalyzerConfigSet.GetValue(CancellationToken.None).GetOptionsForSourcePath(textFile.Path));
            }

            // PROTOTYPE: why isn't this just a provided implementation?
            private sealed class WorkspaceAnalyzerConfigOptions : AnalyzerConfigOptions
            {
                private readonly ImmutableDictionary<string, string> _analyzerOptions;

                public WorkspaceAnalyzerConfigOptions(AnalyzerConfigOptionsResult analyzerConfigOptions)
                {
                    _analyzerOptions = analyzerConfigOptions.AnalyzerOptions;
                }

328
                public override bool TryGetValue(string key, [NotNullWhen(returnValue: true)] out string? value) => _analyzerOptions.TryGetValue(key, out value);
329 330 331 332
            }
        }

        private static ValueSource<AnalyzerConfigSet> ComputeAnalyzerConfigSetValueSource(IEnumerable<AnalyzerConfigDocumentState> analyzerConfigDocumentStates)
333
        {
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
            return new AsyncLazy<AnalyzerConfigSet>(
                asynchronousComputeFunction: async cancellationToken =>
                {
                    var tasks = analyzerConfigDocumentStates.Select(a => a.GetAnalyzerConfigAsync(cancellationToken));
                    var analyzerConfigs = await Task.WhenAll(tasks).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();

                    return AnalyzerConfigSet.Create(analyzerConfigs);
                },
                synchronousComputeFunction: cancellationToken =>
                {
                    var analyzerConfigs = analyzerConfigDocumentStates.SelectAsArray(a => a.GetAnalyzerConfig(cancellationToken));
                    return AnalyzerConfigSet.Create(analyzerConfigs);
                },
                cacheResult: true);
350 351
        }

P
Pilchie 已提交
352 353
        public Task<VersionStamp> GetLatestDocumentVersionAsync(CancellationToken cancellationToken)
        {
354
            return _lazyLatestDocumentVersion.GetValueAsync(cancellationToken);
P
Pilchie 已提交
355 356 357 358
        }

        public Task<VersionStamp> GetLatestDocumentTopLevelChangeVersionAsync(CancellationToken cancellationToken)
        {
359
            return _lazyLatestDocumentTopLevelChangeVersion.GetValueAsync(cancellationToken);
P
Pilchie 已提交
360 361
        }

C
CyrusNajmabadi 已提交
362
        public async Task<VersionStamp> GetSemanticVersionAsync(CancellationToken cancellationToken = default)
P
Pilchie 已提交
363 364 365 366 367 368
        {
            var docVersion = await this.GetLatestDocumentTopLevelChangeVersionAsync(cancellationToken).ConfigureAwait(false);
            return docVersion.GetNewerVersion(this.Version);
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
369
        public ProjectId Id => this.ProjectInfo.Id;
P
Pilchie 已提交
370 371

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
372
        public string? FilePath => this.ProjectInfo.FilePath;
P
Pilchie 已提交
373 374

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
375
        public string? OutputFilePath => this.ProjectInfo.OutputFilePath;
P
Pilchie 已提交
376

377
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
378
        public string? OutputRefFilePath => this.ProjectInfo.OutputRefFilePath;
379

380
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
381
        public string? DefaultNamespace => this.ProjectInfo.DefaultNamespace;
382

P
Pilchie 已提交
383
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
384
        public HostLanguageServices LanguageServices => _languageServices;
P
Pilchie 已提交
385

386 387 388
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public string Language => LanguageServices.Language;

P
Pilchie 已提交
389
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
390
        public string Name => this.ProjectInfo.Name;
P
Pilchie 已提交
391

392
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
393
        public bool IsSubmission => this.ProjectInfo.IsSubmission;
394

P
Pilchie 已提交
395
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
396
        public Type? HostObjectType => this.ProjectInfo.HostObjectType;
P
Pilchie 已提交
397

398
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
399
        public bool SupportsCompilation => this.LanguageServices.GetService<ICompilationFactoryService>() != null;
400

P
Pilchie 已提交
401
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
402
        public VersionStamp Version => this.ProjectInfo.Version;
P
Pilchie 已提交
403 404

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
405
        public ProjectInfo ProjectInfo => _projectInfo;
P
Pilchie 已提交
406 407

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
408
        public string AssemblyName => this.ProjectInfo.AssemblyName;
P
Pilchie 已提交
409

410
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
411
        public CompilationOptions? CompilationOptions => this.ProjectInfo.CompilationOptions;
412

P
Pilchie 已提交
413
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
414
        public ParseOptions? ParseOptions => this.ProjectInfo.ParseOptions;
P
Pilchie 已提交
415

416
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
        public IReadOnlyList<MetadataReference> MetadataReferences => this.ProjectInfo.MetadataReferences;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public IReadOnlyList<AnalyzerReference> AnalyzerReferences => this.ProjectInfo.AnalyzerReferences;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public IReadOnlyList<ProjectReference> ProjectReferences => this.ProjectInfo.ProjectReferences;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public bool HasAllInformation => this.ProjectInfo.HasAllInformation;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public bool HasDocuments => _documentIds.Count > 0;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
432
        public IEnumerable<DocumentState> OrderedDocumentStates => this.DocumentIds.Select(GetDocumentState)!;
433 434 435 436 437 438 439

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public IReadOnlyList<DocumentId> DocumentIds => _documentIds;

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public IReadOnlyList<DocumentId> AdditionalDocumentIds => _additionalDocumentIds;

440 441 442 443 444
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        // Regular documents and additionald documents have an ordering, and so we maintain lists of the IDs in order; in the case of analyzerconfig documents,
        // we don't define a workspace ordering because they are ordered via fancier algorithms in the compiler based on directory depth.
        public IEnumerable<DocumentId> AnalyzerConfigDocumentIds => _analyzerConfigDocumentStates.Keys;

445
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
446
        public IImmutableDictionary<DocumentId, DocumentState> DocumentStates => _documentStates;
447 448

        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
449
        public IImmutableDictionary<DocumentId, TextDocumentState> AdditionalDocumentStates => _additionalDocumentStates;
450

451 452 453
        [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
        public IImmutableDictionary<DocumentId, AnalyzerConfigDocumentState> AnalyzerConfigDocumentStates => _analyzerConfigDocumentStates;

P
Pilchie 已提交
454 455
        public bool ContainsDocument(DocumentId documentId)
        {
456
            return _documentStates.ContainsKey(documentId);
P
Pilchie 已提交
457 458
        }

459 460
        public bool ContainsAdditionalDocument(DocumentId documentId)
        {
461
            return _additionalDocumentStates.ContainsKey(documentId);
462 463
        }

464 465 466 467 468
        public bool ContainsAnalyzerConfigDocument(DocumentId documentId)
        {
            return _analyzerConfigDocumentStates.ContainsKey(documentId);
        }

469
        public DocumentState? GetDocumentState(DocumentId documentId)
P
Pilchie 已提交
470
        {
C
CyrusNajmabadi 已提交
471
            _documentStates.TryGetValue(documentId, out var state);
P
Pilchie 已提交
472 473 474
            return state;
        }

475
        public TextDocumentState? GetAdditionalDocumentState(DocumentId documentId)
476
        {
C
CyrusNajmabadi 已提交
477
            _additionalDocumentStates.TryGetValue(documentId, out var state);
478 479 480
            return state;
        }

481
        public AnalyzerConfigDocumentState? GetAnalyzerConfigDocumentState(DocumentId documentId)
482 483 484 485 486
        {
            _analyzerConfigDocumentStates.TryGetValue(documentId, out var state);
            return state;
        }

P
Pilchie 已提交
487
        private ProjectState With(
488 489 490 491 492 493 494 495 496
            ProjectInfo? projectInfo = null,
            ImmutableList<DocumentId>? documentIds = null,
            ImmutableList<DocumentId>? additionalDocumentIds = null,
            ImmutableSortedDictionary<DocumentId, DocumentState>? documentStates = null,
            ImmutableSortedDictionary<DocumentId, TextDocumentState>? additionalDocumentStates = null,
            ImmutableSortedDictionary<DocumentId, AnalyzerConfigDocumentState>? analyzerConfigDocumentStates = null,
            AsyncLazy<VersionStamp>? latestDocumentVersion = null,
            AsyncLazy<VersionStamp>? latestDocumentTopLevelChangeVersion = null,
            ValueSource<AnalyzerConfigSet>? analyzerConfigSet = null)
P
Pilchie 已提交
497 498
        {
            return new ProjectState(
499 500 501
                projectInfo ?? _projectInfo,
                _languageServices,
                _solutionServices,
502 503
                documentIds ?? _documentIds,
                additionalDocumentIds ?? _additionalDocumentIds,
504 505
                documentStates ?? _documentStates,
                additionalDocumentStates ?? _additionalDocumentStates,
506
                analyzerConfigDocumentStates ?? _analyzerConfigDocumentStates,
507
                latestDocumentVersion ?? _lazyLatestDocumentVersion,
508 509
                latestDocumentTopLevelChangeVersion ?? _lazyLatestDocumentTopLevelChangeVersion,
                analyzerConfigSet ?? _lazyAnalyzerConfigSet);
P
Pilchie 已提交
510 511
        }

512 513 514 515 516 517 518 519 520 521
        public ProjectState UpdateName(string name)
        {
            if (name == this.Name)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithName(name).WithVersion(this.Version.GetNewerVersion()));
        }

522
        public ProjectState UpdateFilePath(string? filePath)
523 524 525 526 527 528 529 530 531
        {
            if (filePath == this.FilePath)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithFilePath(filePath).WithVersion(this.Version.GetNewerVersion()));
        }

P
Pilchie 已提交
532 533 534 535 536 537 538 539 540 541
        public ProjectState UpdateAssemblyName(string assemblyName)
        {
            if (assemblyName == this.AssemblyName)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithAssemblyName(assemblyName).WithVersion(this.Version.GetNewerVersion()));
        }

542
        public ProjectState UpdateOutputFilePath(string? outputFilePath)
P
Pilchie 已提交
543 544 545 546 547 548 549 550 551
        {
            if (outputFilePath == this.OutputFilePath)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithOutputFilePath(outputFilePath).WithVersion(this.Version.GetNewerVersion()));
        }

552
        public ProjectState UpdateOutputRefFilePath(string? outputRefFilePath)
553 554 555 556 557 558
        {
            if (outputRefFilePath == this.OutputRefFilePath)
            {
                return this;
            }

559
            return this.With(projectInfo: this.ProjectInfo.WithOutputRefFilePath(outputRefFilePath).WithVersion(this.Version.GetNewerVersion()));
560 561
        }

562
        public ProjectState UpdateDefaultNamespace(string? defaultNamespace)
563 564 565 566 567 568 569 570 571
        {
            if (defaultNamespace == this.DefaultNamespace)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithDefaultNamespace(defaultNamespace).WithVersion(this.Version.GetNewerVersion()));
        }

P
Pilchie 已提交
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
        public ProjectState UpdateCompilationOptions(CompilationOptions options)
        {
            if (options == this.CompilationOptions)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithCompilationOptions(options).WithVersion(this.Version.GetNewerVersion()));
        }

        public ProjectState UpdateParseOptions(ParseOptions options)
        {
            if (options == this.ParseOptions)
            {
                return this;
            }

            // update parse options for all documents too
590
            var docMap = _documentStates;
P
Pilchie 已提交
591

592
            foreach (var (docId, oldDocState) in _documentStates)
P
Pilchie 已提交
593 594 595 596 597 598 599 600 601 602
            {
                var newDocState = oldDocState.UpdateParseOptions(options);
                docMap = docMap.SetItem(docId, newDocState);
            }

            return this.With(
                projectInfo: this.ProjectInfo.WithParseOptions(options).WithVersion(this.Version.GetNewerVersion()),
                documentStates: docMap);
        }

603 604 605 606 607 608 609 610 611 612
        public ProjectState UpdateHasAllInformation(bool hasAllInformation)
        {
            if (hasAllInformation == this.HasAllInformation)
            {
                return this;
            }

            return this.With(projectInfo: this.ProjectInfo.WithHasAllInformation(hasAllInformation).WithVersion(this.Version.GetNewerVersion()));
        }

P
Pilchie 已提交
613 614 615 616 617 618 619
        public static bool IsSameLanguage(ProjectState project1, ProjectState project2)
        {
            return project1.LanguageServices == project2.LanguageServices;
        }

        public ProjectState RemoveProjectReference(ProjectReference projectReference)
        {
620
            Debug.Assert(this.ProjectReferences.Contains(projectReference));
P
Pilchie 已提交
621 622

            return this.With(
623
                projectInfo: this.ProjectInfo.WithProjectReferences(this.ProjectReferences.ToImmutableArray().Remove(projectReference)).WithVersion(this.Version.GetNewerVersion()));
P
Pilchie 已提交
624 625 626 627 628 629 630
        }

        public ProjectState AddProjectReferences(IEnumerable<ProjectReference> projectReferences)
        {
            var newProjectRefs = this.ProjectReferences;
            foreach (var projectReference in projectReferences)
            {
631
                Debug.Assert(!newProjectRefs.Contains(projectReference));
632
                newProjectRefs = newProjectRefs.ToImmutableArray().Add(projectReference);
P
Pilchie 已提交
633 634 635 636 637 638 639 640 641
            }

            return this.With(
                projectInfo: this.ProjectInfo.WithProjectReferences(newProjectRefs).WithVersion(this.Version.GetNewerVersion()));
        }

        public ProjectState WithProjectReferences(IEnumerable<ProjectReference> projectReferences)
        {
            return this.With(
642
                projectInfo: this.ProjectInfo.WithProjectReferences(projectReferences).WithVersion(this.Version.GetNewerVersion()));
P
Pilchie 已提交
643 644 645 646
        }

        public ProjectState AddMetadataReference(MetadataReference toMetadata)
        {
647
            Debug.Assert(!this.MetadataReferences.Contains(toMetadata));
P
Pilchie 已提交
648 649

            return this.With(
650
                projectInfo: this.ProjectInfo.WithMetadataReferences(this.MetadataReferences.ToImmutableArray().Add(toMetadata)).WithVersion(this.Version.GetNewerVersion()));
P
Pilchie 已提交
651 652 653 654
        }

        public ProjectState RemoveMetadataReference(MetadataReference toMetadata)
        {
655
            Debug.Assert(this.MetadataReferences.Contains(toMetadata));
P
Pilchie 已提交
656 657

            return this.With(
658
                projectInfo: this.ProjectInfo.WithMetadataReferences(this.MetadataReferences.ToImmutableArray().Remove(toMetadata)).WithVersion(this.Version.GetNewerVersion()));
P
Pilchie 已提交
659 660 661 662 663 664 665
        }

        public ProjectState AddMetadataReferences(IEnumerable<MetadataReference> metadataReferences)
        {
            var newMetaRefs = this.MetadataReferences;
            foreach (var metadataReference in metadataReferences)
            {
666
                Debug.Assert(!newMetaRefs.Contains(metadataReference));
667
                newMetaRefs = newMetaRefs.ToImmutableArray().Add(metadataReference);
P
Pilchie 已提交
668 669 670 671 672 673 674 675 676
            }

            return this.With(
                projectInfo: this.ProjectInfo.WithMetadataReferences(newMetaRefs).WithVersion(this.Version.GetNewerVersion()));
        }

        public ProjectState WithMetadataReferences(IEnumerable<MetadataReference> metadataReferences)
        {
            return this.With(
677
                projectInfo: this.ProjectInfo.WithMetadataReferences(metadataReferences).WithVersion(this.Version.GetNewerVersion()));
P
Pilchie 已提交
678 679
        }

680
        public ProjectState AddAnalyzerReference(AnalyzerReference analyzerReference)
681
        {
682
            Debug.Assert(!this.AnalyzerReferences.Contains(analyzerReference));
683 684

            return this.With(
685
                projectInfo: this.ProjectInfo.WithAnalyzerReferences(this.AnalyzerReferences.ToImmutableArray().Add(analyzerReference)).WithVersion(this.Version.GetNewerVersion()));
686 687
        }

688
        public ProjectState RemoveAnalyzerReference(AnalyzerReference analyzerReference)
689
        {
690
            Debug.Assert(this.AnalyzerReferences.Contains(analyzerReference));
691 692

            return this.With(
693
                projectInfo: this.ProjectInfo.WithAnalyzerReferences(this.AnalyzerReferences.ToImmutableArray().Remove(analyzerReference)).WithVersion(this.Version.GetNewerVersion()));
694 695
        }

696
        public ProjectState AddAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
697
        {
698 699
            var newAnalyzerReferences = this.AnalyzerReferences;
            foreach (var analyzerReference in analyzerReferences)
700
            {
701
                Debug.Assert(!newAnalyzerReferences.Contains(analyzerReference));
702
                newAnalyzerReferences = newAnalyzerReferences.ToImmutableArray().Add(analyzerReference);
703 704 705
            }

            return this.With(
706
                projectInfo: this.ProjectInfo.WithAnalyzerReferences(newAnalyzerReferences).WithVersion(this.Version.GetNewerVersion()));
707 708
        }

709
        public ProjectState WithAnalyzerReferences(IEnumerable<AnalyzerReference> analyzerReferences)
710 711
        {
            return this.With(
712
                projectInfo: this.ProjectInfo.WithAnalyzerReferences(analyzerReferences).WithVersion(this.Version.GetNewerVersion()));
713 714
        }

715
        public ProjectState AddDocuments(ImmutableArray<DocumentState> documents)
P
Pilchie 已提交
716
        {
717
            Debug.Assert(!documents.Any(d => this.DocumentStates.ContainsKey(d.Id)));
P
Pilchie 已提交
718 719

            return this.With(
720
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
721 722
                documentIds: _documentIds.AddRange(documents.Select(d => d.Id)),
                documentStates: _documentStates.AddRange(documents.Select(d => KeyValuePairUtil.Create(d.Id, d))));
P
Pilchie 已提交
723 724
        }

725
        public ProjectState AddAdditionalDocuments(ImmutableArray<TextDocumentState> documents)
726
        {
727
            Debug.Assert(!documents.Any(d => this.AdditionalDocumentStates.ContainsKey(d.Id)));
728 729

            return this.With(
730
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
731 732
                additionalDocumentIds: _additionalDocumentIds.AddRange(documents.Select(d => d.Id)),
                additionalDocumentStates: _additionalDocumentStates.AddRange(documents.Select(d => KeyValuePairUtil.Create(d.Id, d))));
733 734
        }

735 736 737 738
        public ProjectState AddAnalyzerConfigDocuments(ImmutableArray<AnalyzerConfigDocumentState> documents)
        {
            Debug.Assert(!documents.Any(d => this._analyzerConfigDocumentStates.ContainsKey(d.Id)));

739 740 741 742 743 744 745 746 747 748 749 750 751
            var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.AddRange(documents.Select(d => KeyValuePairUtil.Create(d.Id, d)));

            return CreateNewStateForChangedAnalyzerConfigDocuments(newAnalyzerConfigDocumentStates);
        }

        private ProjectState CreateNewStateForChangedAnalyzerConfigDocuments(ImmutableSortedDictionary<DocumentId, AnalyzerConfigDocumentState> newAnalyzerConfigDocumentStates)
        {
            var newAnalyzerConfigSet = ComputeAnalyzerConfigSetValueSource(newAnalyzerConfigDocumentStates.Values);

            // The addition of any .editorconfig can modify the diagnostic reporting options that are on
            // a specific syntax tree; therefore we must update all our syntax trees.
            var docMap = _documentStates;

752
            foreach (var (docId, oldDocState) in _documentStates)
753 754 755 756 757
            {
                var newDocState = oldDocState.UpdateAnalyzerConfigSet(newAnalyzerConfigSet);
                docMap = docMap.SetItem(docId, newDocState);
            }

758 759
            return this.With(
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
760 761 762
                analyzerConfigDocumentStates: newAnalyzerConfigDocumentStates,
                documentStates: docMap,
                analyzerConfigSet: newAnalyzerConfigSet);
763 764
        }

P
Pilchie 已提交
765 766
        public ProjectState RemoveDocument(DocumentId documentId)
        {
767
            Debug.Assert(this.DocumentStates.ContainsKey(documentId));
P
Pilchie 已提交
768 769

            return this.With(
770
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
771
                documentIds: _documentIds.Remove(documentId),
772
                documentStates: _documentStates.Remove(documentId));
P
Pilchie 已提交
773 774
        }

775 776
        public ProjectState RemoveAdditionalDocument(DocumentId documentId)
        {
777
            Debug.Assert(this.AdditionalDocumentStates.ContainsKey(documentId));
778 779

            return this.With(
780
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
781
                additionalDocumentIds: _additionalDocumentIds.Remove(documentId),
782
                additionalDocumentStates: _additionalDocumentStates.Remove(documentId));
783 784
        }

785 786
        public ProjectState RemoveAnalyzerConfigDocument(DocumentId documentId)
        {
787
            Debug.Assert(_analyzerConfigDocumentStates.ContainsKey(documentId));
788

789 790 791
            var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.Remove(documentId);

            return CreateNewStateForChangedAnalyzerConfigDocuments(newAnalyzerConfigDocumentStates);
792 793
        }

P
Pilchie 已提交
794 795 796
        public ProjectState RemoveAllDocuments()
        {
            return this.With(
797
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()).WithDocuments(SpecializedCollections.EmptyEnumerable<DocumentInfo>()),
798
                documentIds: ImmutableList<DocumentId>.Empty,
799
                documentStates: ImmutableSortedDictionary.Create<DocumentId, DocumentState>(DocumentIdComparer.Instance));
P
Pilchie 已提交
800 801 802 803
        }

        public ProjectState UpdateDocument(DocumentState newDocument, bool textChanged, bool recalculateDependentVersions)
        {
804
            Debug.Assert(this.ContainsDocument(newDocument.Id));
P
Pilchie 已提交
805

806
            var oldDocument = this.GetDocumentState(newDocument.Id)!;
P
Pilchie 已提交
807 808 809 810 811
            if (oldDocument == newDocument)
            {
                return this;
            }

812
            var newDocumentStates = _documentStates.SetItem(newDocument.Id, newDocument);
P
Pilchie 已提交
813
            GetLatestDependentVersions(
814
                newDocumentStates, _additionalDocumentStates, oldDocument, newDocument, recalculateDependentVersions, textChanged,
C
CyrusNajmabadi 已提交
815
                out var dependentDocumentVersion, out var dependentSemanticVersion);
P
Pilchie 已提交
816 817 818 819 820 821 822

            return this.With(
                documentStates: newDocumentStates,
                latestDocumentVersion: dependentDocumentVersion,
                latestDocumentTopLevelChangeVersion: dependentSemanticVersion);
        }

823 824
        public ProjectState UpdateAdditionalDocument(TextDocumentState newDocument, bool textChanged, bool recalculateDependentVersions)
        {
825
            Debug.Assert(this.ContainsAdditionalDocument(newDocument.Id));
826

827
            var oldDocument = this.GetAdditionalDocumentState(newDocument.Id)!;
828 829 830 831 832
            if (oldDocument == newDocument)
            {
                return this;
            }

833
            var newDocumentStates = _additionalDocumentStates.SetItem(newDocument.Id, newDocument);
834
            GetLatestDependentVersions(
835
                _documentStates, newDocumentStates, oldDocument, newDocument, recalculateDependentVersions, textChanged,
C
CyrusNajmabadi 已提交
836
                out var dependentDocumentVersion, out var dependentSemanticVersion);
837 838 839 840 841 842 843

            return this.With(
                additionalDocumentStates: newDocumentStates,
                latestDocumentVersion: dependentDocumentVersion,
                latestDocumentTopLevelChangeVersion: dependentSemanticVersion);
        }

844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
        public ProjectState UpdateAnalyzerConfigDocument(AnalyzerConfigDocumentState newDocument, bool textChanged, bool recalculateDependentVersions)
        {
            Debug.Assert(this.ContainsAnalyzerConfigDocument(newDocument.Id));

            var oldDocument = this.GetAnalyzerConfigDocumentState(newDocument.Id);
            if (oldDocument == newDocument)
            {
                return this;
            }

            var newDocumentStates = _analyzerConfigDocumentStates.SetItem(newDocument.Id, newDocument);

            return CreateNewStateForChangedAnalyzerConfigDocuments(newDocumentStates);
        }

859 860 861 862 863 864 865 866 867 868 869 870
        public ProjectState UpdateDocumentsOrder(ImmutableList<DocumentId> documentIds)
        {
            if (documentIds.IsEmpty)
            {
                throw new ArgumentOutOfRangeException("The specified documents are empty.", nameof(documentIds));
            }

            if (documentIds.Count != _documentIds.Count)
            {
                throw new ArgumentException($"The specified documents do not equal the project document count.", nameof(documentIds));
            }

T
TIHan 已提交
871 872 873
            var hasOrderChanged = false;

            for (var i = 0; i < documentIds.Count; ++i)
874
            {
T
TIHan 已提交
875 876 877
                var documentId = documentIds[i];

                if (!ContainsDocument(documentId))
878 879 880
                {
                    throw new InvalidOperationException($"The document '{documentId}' does not exist in the project.");
                }
T
TIHan 已提交
881 882 883 884 885 886 887 888 889 890

                if (DocumentIds[i] != documentId)
                {
                    hasOrderChanged = true;
                }
            }

            if (!hasOrderChanged)
            {
                return this;
891 892 893
            }

            return this.With(
T
TIHan 已提交
894
                projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
895 896 897
                documentIds: documentIds);
        }

P
Pilchie 已提交
898
        private void GetLatestDependentVersions(
899 900
            IImmutableDictionary<DocumentId, DocumentState> newDocumentStates,
            IImmutableDictionary<DocumentId, TextDocumentState> newAdditionalDocumentStates,
901
            TextDocumentState oldDocument, TextDocumentState newDocument,
P
Pilchie 已提交
902 903 904 905 906 907 908 909
            bool recalculateDependentVersions, bool textChanged,
            out AsyncLazy<VersionStamp> dependentDocumentVersion, out AsyncLazy<VersionStamp> dependentSemanticVersion)
        {
            var recalculateDocumentVersion = false;
            var recalculateSemanticVersion = false;

            if (recalculateDependentVersions)
            {
C
CyrusNajmabadi 已提交
910
                if (oldDocument.TryGetTextVersion(out var oldVersion))
P
Pilchie 已提交
911
                {
C
CyrusNajmabadi 已提交
912
                    if (!_lazyLatestDocumentVersion.TryGetValue(out var documentVersion) || documentVersion == oldVersion)
P
Pilchie 已提交
913 914 915 916
                    {
                        recalculateDocumentVersion = true;
                    }

C
CyrusNajmabadi 已提交
917
                    if (!_lazyLatestDocumentTopLevelChangeVersion.TryGetValue(out var semanticVersion) || semanticVersion == oldVersion)
P
Pilchie 已提交
918 919 920 921 922 923 924
                    {
                        recalculateSemanticVersion = true;
                    }
                }
            }

            dependentDocumentVersion = recalculateDocumentVersion ?
925
                new AsyncLazy<VersionStamp>(c => ComputeLatestDocumentVersionAsync(newDocumentStates, newAdditionalDocumentStates, c), cacheResult: true) :
P
Pilchie 已提交
926 927
                textChanged ?
                    new AsyncLazy<VersionStamp>(newDocument.GetTextVersionAsync, cacheResult: true) :
928
                    _lazyLatestDocumentVersion;
P
Pilchie 已提交
929 930

            dependentSemanticVersion = recalculateSemanticVersion ?
931
                new AsyncLazy<VersionStamp>(c => ComputeLatestDocumentTopLevelChangeVersionAsync(newDocumentStates, newAdditionalDocumentStates, c), cacheResult: true) :
P
Pilchie 已提交
932
                textChanged ?
933
                    CreateLazyLatestDocumentTopLevelChangeVersion(newDocument, newDocumentStates, newAdditionalDocumentStates) :
934
                    _lazyLatestDocumentTopLevelChangeVersion;
P
Pilchie 已提交
935
        }
936 937 938 939 940 941 942 943 944 945 946 947 948 949

        private sealed class DocumentIdComparer : IComparer<DocumentId>
        {
            public static IComparer<DocumentId> Instance = new DocumentIdComparer();

            private DocumentIdComparer()
            {
            }

            public int Compare(DocumentId x, DocumentId y)
            {
                return x.Id.CompareTo(y.Id);
            }
        }
P
Pilchie 已提交
950 951
    }
}