From 84931775ae116041fdf5c8f50632690d80b6407e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 10 Feb 2025 12:31:00 -0800 Subject: [PATCH 1/8] Cache based on project state not project. --- ...cIncrementalAnalyzer.CompilationManager.cs | 31 +++++++------------ ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 2 +- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index f04fa0a778fa9..e6b2a8c4016db 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -24,7 +24,7 @@ internal partial class DiagnosticAnalyzerService /// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the /// next caller. /// - private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); + private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); private static async Task GetOrCreateCompilationWithAnalyzersAsync( Project project, @@ -36,22 +36,25 @@ internal partial class DiagnosticAnalyzerService if (!project.SupportsCompilation) return null; + var projectState = project.State; + // Make sure the cached pair was computed with at least the same state sets we're asking about. if not, // recompute and cache with the new state sets. - if (!s_projectToCompilationWithAnalyzers.TryGetValue(project, out var tupleBox) || + if (!s_projectToCompilationWithAnalyzers.TryGetValue(projectState, out var tupleBox) || !analyzers.IsSubsetOf(tupleBox.Value.analyzers)) { - var compilationWithAnalyzersPair = await CreateCompilationWithAnalyzersAsync().ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilationWithAnalyzersPair = CreateCompilationWithAnalyzers(projectState, compilation); tupleBox = new((analyzers, compilationWithAnalyzersPair)); // Make a best effort attempt to store the latest computed value against these state sets. If this // fails (because another thread interleaves with this), that's ok. We still return the pair we // computed, so our caller will still see the right data - s_projectToCompilationWithAnalyzers.Remove(project); + s_projectToCompilationWithAnalyzers.Remove(projectState); // Intentionally ignore the result of this. We still want to use the value we computed above, even if // another thread interleaves and sets a different value. - s_projectToCompilationWithAnalyzers.GetValue(project, _ => tupleBox); + s_projectToCompilationWithAnalyzers.GetValue(projectState, _ => tupleBox); } return tupleBox.Value.compilationWithAnalyzersPair; @@ -59,13 +62,12 @@ internal partial class DiagnosticAnalyzerService // // Should only be called on a that . // - async Task CreateCompilationWithAnalyzersAsync() + CompilationWithAnalyzersPair? CreateCompilationWithAnalyzers( + ProjectState project, Compilation compilation) { var projectAnalyzers = analyzers.WhereAsArray(static (s, info) => !info.IsHostAnalyzer(s), hostAnalyzerInfo); var hostAnalyzers = analyzers.WhereAsArray(static (s, info) => info.IsHostAnalyzer(s), hostAnalyzerInfo); - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - // Create driver that holds onto compilation and associated analyzers var filteredProjectAnalyzers = projectAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); var filteredHostAnalyzers = hostAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()); @@ -79,13 +81,10 @@ internal partial class DiagnosticAnalyzerService return null; } - Contract.ThrowIfFalse(project.SupportsCompilation); - AssertCompilation(project, compilation); - // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to // async being used with synchronous blocking concurrency. var projectAnalyzerOptions = new CompilationWithAnalyzersOptions( - options: project.AnalyzerOptions, + options: project.ProjectAnalyzerOptions, onAnalyzerException: null, analyzerExceptionFilter: GetAnalyzerExceptionFilter(), concurrentAnalysis: false, @@ -122,12 +121,4 @@ Func GetAnalyzerExceptionFilter() } } } - - [Conditional("DEBUG")] - private static void AssertCompilation(Project project, Compilation compilation1) - { - // given compilation must be from given project. - Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2)); - Contract.ThrowIfFalse(compilation1 == compilation2); - } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index f33f2e14ebf7e..f50572eac27ff 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -25,7 +25,7 @@ private partial class DiagnosticIncrementalAnalyzer /// cref="DiagnosticGetter.ProduceDiagnosticsAsync"/> to speed up subsequent calls through the normal entry points as long as the project hasn't changed at all. /// - private readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); + private readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { From 80ac90659e62f5c60cce23b3f5f42d9fdc68a16e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 10 Feb 2025 12:55:55 -0800 Subject: [PATCH 2/8] Cache based on project state not project. --- ...ticIncrementalAnalyzer.HostAnalyzerInfo.cs | 16 ++++++---- ...ntalAnalyzer.StateManager.ProjectStates.cs | 17 +++++----- ...gnosticIncrementalAnalyzer.StateManager.cs | 14 ++++---- .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 2 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 10 ++++-- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 15 ++++++--- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 32 +++++++++++++------ .../InProcOrRemoteHostAnalyzerRunner.cs | 2 +- .../Diagnostics/HostDiagnosticAnalyzers.cs | 4 +-- .../Portable/Workspace/Solution/Project.cs | 3 -- 10 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs index 292ac0a5d21d9..9a917b7ff7ef5 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs @@ -17,13 +17,14 @@ private partial class DiagnosticIncrementalAnalyzer { private partial class StateManager { - private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(Project project, ProjectAnalyzerInfo projectAnalyzerInfo) + private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo( + SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo) { - var key = new HostAnalyzerInfoKey(project.Language, project.State.HasSdkCodeStyleAnalyzers, project.Solution.SolutionState.Analyzers.HostAnalyzerReferences); + var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences); // Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the // Host fallback options. These ids will be used when building up the Host and Project analyzer collections. - var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(project); - var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (project.Solution.SolutionState.Analyzers, referenceIdsToRedirect)); + var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project); + var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect)); return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers); static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet ReferenceIdsToRedirect) state) @@ -65,14 +66,15 @@ static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey ar } } - private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers(Project project) + private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers( + SolutionState solution, ProjectState project) { - if (project.State.HasSdkCodeStyleAnalyzers) + if (project.HasSdkCodeStyleAnalyzers) { // When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the // Features analyzers. We need to then treat the Features analyzers as Project analyzers so // they do not get access to the Host fallback options. - return GetFeaturesAnalyzerReferenceIds(project.Solution.SolutionState.Analyzers); + return GetFeaturesAnalyzerReferenceIds(solution.Analyzers); } return []; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index c3eca357edf17..849e5821bdc8f 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -41,7 +41,7 @@ internal ProjectAnalyzerInfo( } } - private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(Project project) + private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project) { // check if the analyzer references have changed since the last time we updated the map: // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap @@ -54,17 +54,17 @@ internal ProjectAnalyzerInfo( return null; } - private async Task GetOrCreateProjectAnalyzerInfoAsync(Project project, CancellationToken cancellationToken) - => TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); + private async Task GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken) + => TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(Project project) + private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project) { if (project.AnalyzerReferences.Count == 0) { return ProjectAnalyzerInfo.Default; } - var hostAnalyzers = project.Solution.SolutionState.Analyzers; + var hostAnalyzers = solution.Analyzers; var analyzersPerReference = hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); if (analyzersPerReference.Count == 0) { @@ -78,14 +78,15 @@ private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(Project project) // workspace placeholder analyzers. So we should never get host analyzers back here. Contract.ThrowIfTrue(newHostAnalyzers.Count > 0); - var skippedAnalyzersInfo = project.GetSkippedAnalyzersInfo(_analyzerInfoCache); + var skippedAnalyzersInfo = solution.Analyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo); } /// /// Updates the map to the given project snapshot. /// - private async Task UpdateProjectAnalyzerInfoAsync(Project project, CancellationToken cancellationToken) + private async Task UpdateProjectAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { // This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets. using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) @@ -94,7 +95,7 @@ private async Task UpdateProjectAnalyzerInfoAsync(Project p if (projectAnalyzerInfo == null) { - projectAnalyzerInfo = CreateProjectAnalyzerInfo(project); + projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project); // update cache. _projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value); diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index e3efbcf14327a..cce4d08ccf0c9 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -47,17 +47,19 @@ private partial class StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache /// /// Return s for the given . /// - public async Task> GetOrCreateAnalyzersAsync(Project project, CancellationToken cancellationToken) + public async Task> GetOrCreateAnalyzersAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { - var hostAnalyzerInfo = await GetOrCreateHostAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); - var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await GetOrCreateHostAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); return hostAnalyzerInfo.OrderedAllAnalyzers.AddRange(projectAnalyzerInfo.Analyzers); } - public async Task GetOrCreateHostAnalyzerInfoAsync(Project project, CancellationToken cancellationToken) + public async Task GetOrCreateHostAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) { - var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); - return GetOrCreateHostAnalyzerInfo(project, projectAnalyzerInfo); + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return GetOrCreateHostAnalyzerInfo(solution, project, projectAnalyzerInfo); } private static (ImmutableHashSet hostAnalyzers, ImmutableHashSet allAnalyzers) PartitionAnalyzers( diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 630a446d92638..08592ff697389 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -51,7 +51,7 @@ public DiagnosticIncrementalAnalyzer( public async Task> GetAnalyzersForTestingPurposesOnlyAsync(Project project, CancellationToken cancellationToken) { - var analyzers = await _stateManager.GetOrCreateAnalyzersAsync(project, cancellationToken).ConfigureAwait(false); + var analyzers = await _stateManager.GetOrCreateAnalyzersAsync(project.Solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); return analyzers; } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index e4b9a2658b0fc..f88de4dc9f3f9 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -101,8 +101,12 @@ private async Task ProduceDiagnosticsAsync( ArrayBuilder builder, CancellationToken cancellationToken) { - var analyzersForProject = await StateManager.GetOrCreateAnalyzersAsync(project, cancellationToken).ConfigureAwait(false); - var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); + var solution = project.Solution; + var projectState = project.State; + var analyzersForProject = await StateManager.GetOrCreateAnalyzersAsync( + solution.SolutionState, projectState, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync( + solution.SolutionState, projectState, cancellationToken).ConfigureAwait(false); var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a)); var result = await GetOrComputeDiagnosticAnalysisResultsAsync(analyzers).ConfigureAwait(false); @@ -144,7 +148,7 @@ async Task> Ge // (since it runs all analyzers), we still run a paranoia check that the analyzers we care about are // a subset of that call so that we don't accidentally reuse results that would not correspond to // what we are computing ourselves. - if (this.Owner._projectToForceAnalysisData.TryGetValue(project, out var box) && + if (this.Owner._projectToForceAnalysisData.TryGetValue(projectState, out var box) && analyzers.IsSubsetOf(box.Value.analyzers)) { return box.Value.diagnosticAnalysisResults; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index b2ecd328c8678..1ca539fe9461d 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -75,12 +75,15 @@ public static async Task CreateAsync( { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var project = document.Project; + var solution = project.Solution; var unfilteredAnalyzers = await owner._stateManager - .GetOrCreateAnalyzersAsync(document.Project, cancellationToken) + .GetOrCreateAnalyzersAsync(solution.SolutionState, project.State, cancellationToken) .ConfigureAwait(false); var analyzers = unfilteredAnalyzers - .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, document.Project, owner.GlobalOptions)); - var hostAnalyzerInfo = await owner._stateManager.GetOrCreateHostAnalyzerInfoAsync(document.Project, cancellationToken).ConfigureAwait(false); + .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, project, owner.GlobalOptions)); + var hostAnalyzerInfo = await owner._stateManager.GetOrCreateHostAnalyzerInfoAsync( + solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); // Note that some callers, such as diagnostic tagger, might pass in a range equal to the entire document span. // We clear out range for such cases as we are computing full document diagnostics. @@ -284,13 +287,15 @@ private async Task ComputeDocumentDiagnosticsAsync( analyzers = filteredAnalyzers.ToImmutable(); - var hostAnalyzerInfo = await _owner._stateManager.GetOrCreateHostAnalyzerInfoAsync(_document.Project, cancellationToken).ConfigureAwait(false); + var project = _document.Project; + var hostAnalyzerInfo = await _owner._stateManager.GetOrCreateHostAnalyzerInfoAsync( + project.Solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo); var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo); var analysisScope = new DocumentAnalysisScope(_document, span, projectAnalyzers, hostAnalyzers, kind); var executor = new DocumentAnalysisExecutor(analysisScope, _compilationWithAnalyzers, _owner._diagnosticAnalyzerRunner, _isExplicit, _logPerformanceInfo); - var version = await GetDiagnosticVersionAsync(_document.Project, cancellationToken).ConfigureAwait(false); + var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); ImmutableDictionary> diagnosticsMap; if (incrementalAnalysis) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index f50572eac27ff..5f0b5e5a7235e 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -20,21 +20,31 @@ internal partial class DiagnosticAnalyzerService private partial class DiagnosticIncrementalAnalyzer { /// - /// Cached data from a real instance to the cached diagnostic data produced by + /// Cached data from a real instance to the cached diagnostic data produced by /// all the analyzers for the project. This data can then be used by to speed up subsequent calls through the normal entry points as long as the project hasn't changed at all. /// + /// + /// This table is keyed off of but stores data from on + /// it. Specifically . Normally keying off a ProjectState would not be ok + /// as the ProjectState might stay the same while the SolutionState changed. However, that can't happen as + /// SolutionState has the data for Analyzers computed prior to Projects being added, and then never changes. + /// Practically, solution analyzers are the core Roslyn analyzers themselves we distribute, or analyzers shipped + /// by vsix (not nuget). These analyzers do not get loaded after changing *until* VS restarts. + /// private readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { + var projectState = project.State; + try { - if (!_projectToForceAnalysisData.TryGetValue(project, out var box)) + if (!_projectToForceAnalysisData.TryGetValue(projectState, out var box)) { box = new(await ComputeForceAnalyzeProjectAsync().ConfigureAwait(false)); - box = _projectToForceAnalysisData.GetValue(project, _ => box); + box = _projectToForceAnalysisData.GetValue(projectState, _ => box); } using var _ = ArrayBuilder.GetInstance(out var diagnostics); @@ -55,12 +65,13 @@ public async Task> ForceAnalyzeProjectAsync(Proje async Task<(ImmutableArray analyzers, ImmutableDictionary diagnosticAnalysisResults)> ComputeForceAnalyzeProjectAsync() { - var allAnalyzers = await _stateManager.GetOrCreateAnalyzersAsync(project, cancellationToken).ConfigureAwait(false); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); + var solutionState = project.Solution.SolutionState; + var allAnalyzers = await _stateManager.GetOrCreateAnalyzersAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); var fullSolutionAnalysisAnalyzers = allAnalyzers.WhereAsArray( - static (analyzer, arg) => arg.self.IsCandidateForFullSolutionAnalysis(analyzer, arg.hostAnalyzerInfo.IsHostAnalyzer(analyzer), arg.project), - (self: this, project, hostAnalyzerInfo)); + static (analyzer, arg) => arg.self.IsCandidateForFullSolutionAnalysis(analyzer, arg.hostAnalyzerInfo.IsHostAnalyzer(analyzer), arg.projectState), + (self: this, projectState, hostAnalyzerInfo)); var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync( project, fullSolutionAnalysisAnalyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); @@ -70,7 +81,8 @@ public async Task> ForceAnalyzeProjectAsync(Proje } } - private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, bool isHostAnalyzer, Project project) + private bool IsCandidateForFullSolutionAnalysis( + DiagnosticAnalyzer analyzer, bool isHostAnalyzer, ProjectState project) { // PERF: Don't query descriptors for compiler analyzer or workspace load analyzer, always execute them. if (analyzer == FileContentLoadAnalyzer.Instance || @@ -110,7 +122,9 @@ private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, boo var descriptors = DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer); var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); - return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity(arg.CompilationOptions, arg.isHostAnalyzer ? arg.analyzerConfigOptions?.ConfigOptionsWithFallback : arg.analyzerConfigOptions?.ConfigOptionsWithoutFallback, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, (project.CompilationOptions, isHostAnalyzer, analyzerConfigOptions)); + return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity( + arg.CompilationOptions, arg.isHostAnalyzer ? arg.analyzerConfigOptions?.ConfigOptionsWithFallback : arg.analyzerConfigOptions?.ConfigOptionsWithoutFallback, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, + (project.CompilationOptions, isHostAnalyzer, analyzerConfigOptions)); } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs index 1203bb1dabee7..d43c3016d6487 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs @@ -131,7 +131,7 @@ private async Task.Empty; diff --git a/src/Workspaces/Core/Portable/Diagnostics/HostDiagnosticAnalyzers.cs b/src/Workspaces/Core/Portable/Diagnostics/HostDiagnosticAnalyzers.cs index e63221b427381..fb72eccc2dc01 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/HostDiagnosticAnalyzers.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/HostDiagnosticAnalyzers.cs @@ -133,7 +133,7 @@ public ImmutableDictionary> CreateDia /// Create identity and s map for given that /// has only project analyzers /// - public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(Project project) + public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(ProjectState project) => CreateProjectDiagnosticAnalyzersPerReference(project.AnalyzerReferences, project.Language); public ImmutableDictionary> CreateProjectDiagnosticAnalyzersPerReference(IReadOnlyList projectAnalyzerReferences, string language) @@ -296,7 +296,7 @@ private static ImmutableDictionary> M return current; } - public SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(Project project, DiagnosticAnalyzerInfoCache infoCache) + public SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(ProjectState project, DiagnosticAnalyzerInfoCache infoCache) { var box = _skippedHostAnalyzers.GetOrCreateValue(project.AnalyzerReferences); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index c76d53fcca168..260c146144562 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -830,9 +830,6 @@ internal StructuredAnalyzerConfigOptions GetFallbackAnalyzerOptions() private string GetDebuggerDisplay() => this.Name; - internal SkippedHostAnalyzersInfo GetSkippedAnalyzersInfo(DiagnosticAnalyzerInfoCache infoCache) - => Solution.SolutionState.Analyzers.GetSkippedAnalyzersInfo(this, infoCache); - internal async ValueTask GetDocumentAsync(ImmutableArray contentHash, CancellationToken cancellationToken) { var documentId = await State.GetDocumentIdAsync(contentHash, cancellationToken).ConfigureAwait(false); From 494c5e0cac0d99a25db1e6eb99349dc1bb48cf7c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 10 Feb 2025 14:17:26 -0800 Subject: [PATCH 3/8] Update docs --- .../DiagnosticIncrementalAnalyzer.CompilationManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index bf7d35d92cd52..8c218f1c9f159 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -18,8 +18,8 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal partial class DiagnosticAnalyzerService { /// - /// Cached data from a to the last instance created - /// for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of to the last instance + /// created for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of s passed along with the project. As such, we might not be able to use a prior cached /// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the /// next caller. From 8d93475ae96daefa768642c202e8b35cb1a3e247 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 10 Feb 2025 14:17:52 -0800 Subject: [PATCH 4/8] PR feedback --- .../DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 847f66675d9e6..0ceeff1400fa6 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -48,8 +48,8 @@ public async Task> ForceAnalyzeProjectAsync(Proje // Try to add the new computed data to the CWT. But use any existing value that another thread // might have beaten us to storing in it. #if NET - _projectToForceAnalysisData.TryAdd(projectState, box); - Contract.ThrowIfFalse(_projectToForceAnalysisData.TryGetValue(projectState, out box)); + if (!_projectToForceAnalysisData.TryAdd(projectState, box)) + Contract.ThrowIfFalse(_projectToForceAnalysisData.TryGetValue(projectState, out box)); #else box = _projectToForceAnalysisData.GetValue(projectState, _ => box); #endif From a070adda066350f4d61c6a503bffe3085f2024c0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 10 Feb 2025 14:20:36 -0800 Subject: [PATCH 5/8] fix remote side --- .../Services/DiagnosticAnalyzer/DiagnosticComputer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index 3a767508ac893..6ff32cf4ac6e1 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs @@ -356,7 +356,8 @@ private async Task GetDiagnosticsAsync( } } - var skippedAnalyzersInfo = _project.GetSkippedAnalyzersInfo(_analyzerInfoCache); + var skippedAnalyzersInfo = _project.Solution.SolutionState.Analyzers.GetSkippedAnalyzersInfo( + _project.State, _analyzerInfoCache); return await AnalyzeAsync(compilationWithAnalyzers, analyzerToIdMap, projectAnalyzers, hostAnalyzers, skippedAnalyzersInfo, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false); From c86f8c2730f132c93b659a951aab87605048be55 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 13 Feb 2025 16:08:10 -0800 Subject: [PATCH 6/8] Store checlksum --- ...osticIncrementalAnalyzer.CompilationManager.cs | 5 +++-- ...iagnosticIncrementalAnalyzer_GetDiagnostics.cs | 15 ++++++++++----- ...icIncrementalAnalyzer_GetDiagnosticsForSpan.cs | 8 +++++--- ...sticIncrementalAnalyzer_IncrementalAnalyzer.cs | 12 +++++++----- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index b0c6fd68134a7..39cf98993dda9 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -24,7 +24,7 @@ internal partial class DiagnosticAnalyzerService /// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the /// next caller. /// - private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); + private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new(); private static async Task GetOrCreateCompilationWithAnalyzersAsync( Project project, @@ -37,6 +37,7 @@ internal partial class DiagnosticAnalyzerService return null; var projectState = project.State; + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); // Make sure the cached pair was computed with at least the same state sets we're asking about. if not, // recompute and cache with the new state sets. @@ -45,7 +46,7 @@ internal partial class DiagnosticAnalyzerService { var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var compilationWithAnalyzersPair = CreateCompilationWithAnalyzers(projectState, compilation); - tupleBox = new((analyzers, compilationWithAnalyzersPair)); + tupleBox = new((checksum, analyzers, compilationWithAnalyzersPair)); #if NET s_projectToCompilationWithAnalyzers.AddOrUpdate(projectState, tupleBox); diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 5d6c9799b1038..4df57f00f44d9 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -60,8 +60,11 @@ private async Task> ProduceProjectDiagnosticsAsyn { using var _ = ArrayBuilder.GetInstance(out var builder); - var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync(project, cancellationToken).ConfigureAwait(false); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(project, cancellationToken).ConfigureAwait(false); + var solution = project.Solution; + var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync( + solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync( + solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a)); var result = await GetOrComputeDiagnosticAnalysisResultsAsync(analyzers).ConfigureAwait(false); @@ -108,14 +111,16 @@ async Task> Ge // If there was a 'ForceAnalyzeProjectAsync' run for this project, we can piggy back off of the // prior computed/cached results as they will be a superset of the results we want. // - // Note: the caller will loop over *its* analzyers, grabbing from the full set of data we've cached - // for this project, and filtering down further. So it's ok to return this potentially larger set. + // Note: the caller will loop over *its* analyzers, grabbing from the full set of data we've cached for + // this project, and filtering down further. So it's ok to return this potentially larger set. // // Note: While ForceAnalyzeProjectAsync should always run with a larger set of analyzers than us // (since it runs all analyzers), we still run a paranoia check that the analyzers we care about are // a subset of that call so that we don't accidentally reuse results that would not correspond to // what we are computing ourselves. - if (_projectToForceAnalysisData.TryGetValue(project, out var box) && + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); + if (_projectToForceAnalysisData.TryGetValue(project.State, out var box) && + box.Value.checksum == checksum && analyzers.IsSubsetOf(box.Value.analyzers)) { return box.Value.diagnosticAnalysisResults; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index f53626179aa28..48da4ffee222a 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -59,12 +59,14 @@ public async Task> GetDiagnosticsForSpanAsync( { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var project = document.Project; + var solutionState = project.Solution.SolutionState; var unfilteredAnalyzers = await _stateManager - .GetOrCreateAnalyzersAsync(document.Project, cancellationToken) + .GetOrCreateAnalyzersAsync(solutionState, project.State, cancellationToken) .ConfigureAwait(false); var analyzers = unfilteredAnalyzers .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, document.Project, GlobalOptions)); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(document.Project, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); // Note that some callers, such as diagnostic tagger, might pass in a range equal to the entire document span. // We clear out range for such cases as we are computing full document diagnostics. @@ -229,7 +231,7 @@ async Task ComputeDocumentDiagnosticsAsync( analyzers = filteredAnalyzers.ToImmutable(); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(document.Project, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo); var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo); diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index d469514d40904..93490ccb00f0d 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -33,15 +33,17 @@ private partial class DiagnosticIncrementalAnalyzer /// Practically, solution analyzers are the core Roslyn analyzers themselves we distribute, or analyzers shipped /// by vsix (not nuget). These analyzers do not get loaded after changing *until* VS restarts. /// - private readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); + private static readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { var projectState = project.State; + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); try { - if (!_projectToForceAnalysisData.TryGetValue(projectState, out var box)) + if (!_projectToForceAnalysisData.TryGetValue(projectState, out var box) || + box.Value.checksum != checksum) { box = new(await ComputeForceAnalyzeProjectAsync().ConfigureAwait(false)); @@ -57,7 +59,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje using var _ = ArrayBuilder.GetInstance(out var diagnostics); - var (analyzers, projectAnalysisData) = box.Value; + var (_, analyzers, projectAnalysisData) = box.Value; foreach (var analyzer in analyzers) { if (projectAnalysisData.TryGetValue(analyzer, out var analyzerResult)) @@ -71,7 +73,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje throw ExceptionUtilities.Unreachable(); } - async Task<(ImmutableArray analyzers, ImmutableDictionary diagnosticAnalysisResults)> ComputeForceAnalyzeProjectAsync() + async Task<(Checksum checksum, ImmutableArray analyzers, ImmutableDictionary diagnosticAnalysisResults)> ComputeForceAnalyzeProjectAsync() { var solutionState = project.Solution.SolutionState; var allAnalyzers = await _stateManager.GetOrCreateAnalyzersAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); @@ -86,7 +88,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje project, fullSolutionAnalysisAnalyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false); var projectAnalysisData = await ComputeDiagnosticAnalysisResultsAsync(compilationWithAnalyzers, project, fullSolutionAnalysisAnalyzers, cancellationToken).ConfigureAwait(false); - return (fullSolutionAnalysisAnalyzers, projectAnalysisData); + return (checksum, fullSolutionAnalysisAnalyzers, projectAnalysisData); } static bool IsCandidateForFullSolutionAnalysis( From a4f5f696cf99f369a514b8c6739fac0090be4dae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 14 Feb 2025 09:27:46 -0800 Subject: [PATCH 7/8] Merge in main --- eng/config/PublishData.json | 2 +- .../CSharp/Portable/Parser/LanguageParser.cs | 25 +- .../Syntax/Parsing/ForStatementParsingTest.cs | 4473 ++++++++++++++++- .../DiagnosticAnalyzer/AnalyzerDriver.cs | 2 +- .../CachingSemanticModelProvider.cs | 16 +- .../CompilationWithAnalyzers.cs | 6 +- .../SemanticClassifierTests_Regex.cs | 66 + .../DiagnosticAnalyzerDriverTests.cs | 2 + .../Test/CodeFixes/CodeFixServiceTests.cs | 6 +- .../DiagnosticAnalyzerServiceTests.cs | 2 + .../VSTypeScriptHandlerTests.cs | 75 +- .../Diagnostics/DiagnosticServiceTests.vb | 35 + .../Workspaces/EditorTestWorkspace.cs | 22 +- ...ToInterpolatedStringRefactoringProvider.cs | 10 +- ...ertPlaceholderToInterpolatedStringTests.cs | 41 +- .../Providers/SymbolCompletionItem.cs | 38 +- ...ToInterpolatedStringRefactoringProvider.cs | 197 +- .../AbstractLanguageDetector.cs | 2 +- .../EmbeddedLanguageDetector.cs | 84 +- .../Lsif/GeneratorTest/FoldingRangeTests.vb | 3 - ...rverIndexFormat.Generator.UnitTests.vbproj | 1 + ...odeAnalysis.Features.Test.Utilities.csproj | 1 + ...ToInterpolatedStringRefactoringProvider.vb | 2 +- ...rverProtocolTests.InitializationOptions.cs | 0 .../AbstractLanguageServerProtocolTests.cs | 198 +- .../AbstractLspBuildOnlyDiagnosticsTests.cs | 2 +- .../LanguageServer/TestOutputLspLogger.cs | 3 +- .../TestWorkspaceRegistrationService.cs | 14 +- ...guageServer.Protocol.Test.Utilities.csproj | 5 + .../Workspaces/LspTestWorkspace.cs | 1 + .../TestStaticSourceTextContainer.cs | 22 + .../DocumentAnalysisExecutor_Helpers.cs | 4 - ...cIncrementalAnalyzer.CompilationManager.cs | 1 + ...ntalAnalyzer.StateManager.ProjectStates.cs | 6 +- ...gnosticIncrementalAnalyzer.StateManager.cs | 4 - ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 1 - .../CodeActions/CodeActionResolveTests.cs | 114 +- .../CodeActions/CodeActionsTests.cs | 26 +- .../CodeActions/RunCodeActionsTests.cs | 2 +- .../CodeLens/CSharpCodeLensTests.cs | 29 +- .../Commands/ExecuteWorkspaceCommandTests.cs | 2 +- ...ngeConfigurationNotificationHandlerTest.cs | 4 +- .../AbstractPullDiagnosticTestsBase.cs | 3 +- .../AdditionalFileDiagnosticsTests.cs | 12 +- .../Diagnostics/DiagnosticsPullCacheTests.cs | 6 +- .../Diagnostics/NonLocalDiagnosticTests.cs | 2 +- .../Diagnostics/PullDiagnosticTests.cs | 295 +- .../WorkspaceProjectDiagnosticsTests.cs | 4 +- .../InlayHint/CSharpInlayHintTests.cs | 4 +- .../ProtocolUnitTests/MapCode/MapCodeTests.cs | 4 +- .../LspMetadataAsSourceWorkspaceTests.cs | 2 + ...s.LanguageServer.Protocol.UnitTests.csproj | 13 +- .../LspMiscellaneousFilesWorkspaceTests.cs | 1 + .../Options/LspOptionsTests.cs | 2 - .../GetTextDocumentWithContextHandlerTests.cs | 8 +- .../FindAllReferencesHandlerFeaturesTests.cs | 5 +- .../RelatedDocuments/RelatedDocumentsTests.cs | 6 +- .../ProtocolUnitTests/Rename/RenameTests.cs | 10 +- .../SpellCheck/SpellCheckTests.cs | 27 +- .../Symbols/WorkspaceSymbolsTests.cs | 2 +- .../Workspaces/LspWorkspaceManagerTests.cs | 9 +- .../SourceGeneratedDocumentTests.cs | 2 +- .../Apis/Microsoft.CodeAnalysis.txt | 1 + .../DocumentOutlineTestsBase.cs | 26 +- ...o.LanguageServices.CSharp.UnitTests.csproj | 1 + .../Def/LanguageService/AbstractPackage.cs | 7 +- .../AbstractLiveShareRequestHandlerTests.cs | 2 +- ...yntaxEditorBasedCodeRefactoringProvider.cs | 5 +- .../SerializerService_Reference.cs | 39 +- .../TestAnalyzerReferenceByLanguage.cs | 6 +- .../TestDocumentTrackingService.cs | 2 +- .../Workspaces/TestWorkspace`1.cs | 8 +- 72 files changed, 5378 insertions(+), 685 deletions(-) rename src/{LanguageServer/ProtocolUnitTests => EditorFeatures/Test/LanguageServer}/VSTypeScriptHandlerTests.cs (65%) rename src/{EditorFeatures/TestUtilities => LanguageServer/Protocol.TestUtilities}/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs (100%) rename src/{EditorFeatures/TestUtilities => LanguageServer/Protocol.TestUtilities}/LanguageServer/AbstractLanguageServerProtocolTests.cs (83%) rename src/{EditorFeatures/TestUtilities => LanguageServer/Protocol.TestUtilities}/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs (97%) rename src/{EditorFeatures/TestUtilities => LanguageServer/Protocol.TestUtilities}/LanguageServer/TestOutputLspLogger.cs (95%) rename src/{EditorFeatures/TestUtilities => LanguageServer/Protocol.TestUtilities}/LanguageServer/TestWorkspaceRegistrationService.cs (50%) create mode 100644 src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs rename src/{EditorFeatures/TestUtilities/DocumentTracking => Workspaces/CoreTestUtilities/Workspaces}/TestDocumentTrackingService.cs (95%) diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 27e271cf33f5d..da865cfedad8c 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -263,7 +263,7 @@ "vsBranch": "dev/monicaro/versioning", "vsMajorVersion": 18, "insertionTitlePrefix": "[d18.0 P1]", - "insertionCreateDraftPR": false + "insertionCreateDraftPR": true }, "main": { "nugetKind": [ diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 12f19f4de2626..078840f3dd553 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -5050,25 +5050,26 @@ private void ParseVariableDeclarators( } else if (this.CurrentToken.Kind == SyntaxKind.CommaToken) { - // If we see `for (int i = 0, j < ...` then we do not want to consume j as the next declarator. + // If we see `for (int i = 0, i < ...` then we do not want to consume the second 'i' as the next declarator as it + // is more likely that the user meant to write `for (int i = 0; i < ...` instead and accidentally + // used a comma instead of a semicolon. // - // Legal forms here are `for (int i = 0, j; ...` or `for (int i = 0, j = ...` or `for (int i = 0, j)`. + // Note: the legal forms that we must keep parsing as a variable declarator are: // - // We also accept: `for (int i = 0, ;` as that's likely an intermediary state prior to writing the next - // variable. + // for (int i = 0, j, k; ... // identifier comma + // for (int i = 0, j = ... // identifier equals + // for (int i = 0, j; ... // identifier semicolon // - // Anything else we'll treat as as more likely to be the following conditional. + // We also accept: `for (int i = 0, ;` as that's likely an intermediary state prior to writing the + // next variable. Anything else we'll treat as as more likely to be the following conditional. if (flags.HasFlag(VariableFlags.ForStatement) && this.PeekToken(1).Kind != SyntaxKind.SemicolonToken) { - // `int i = 0, ...` where what follows is not an identifier. Don't treat this as the start of a - // second variable. - if (!IsTrueIdentifier(this.PeekToken(1))) - break; + var isLegalVariableDeclaratorStart = + IsTrueIdentifier(this.PeekToken(1)) && + this.PeekToken(2).Kind is SyntaxKind.CommaToken or SyntaxKind.EqualsToken or SyntaxKind.SemicolonToken; - // `int i = 0, j ...` where what follows is not something that continues a variable declaration. - // In this case, treat that `j` as the start of the condition expression instead. - if (this.PeekToken(2).Kind is not (SyntaxKind.SemicolonToken or SyntaxKind.EqualsToken or SyntaxKind.CloseParenToken)) + if (!isLegalVariableDeclaratorStart) break; } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ForStatementParsingTest.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ForStatementParsingTest.cs index 75dcb989303d4..9edfa81c9a381 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ForStatementParsingTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ForStatementParsingTest.cs @@ -213,12 +213,9 @@ public void TestCommaSeparators3() public void TestCommaSeparators4() { UsingStatement("for (int i = 0, i) ;", - // (1,18): error CS1002: ; expected - // for (int i = 0, i) ; - Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 18), - // (1,18): error CS1525: Invalid expression term ')' + // (1,15): error CS1002: ; expected // for (int i = 0, i) ; - Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 18), + Diagnostic(ErrorCode.ERR_SemicolonExpected, ",").WithLocation(1, 15), // (1,18): error CS1002: ; expected // for (int i = 0, i) ; Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 18)); @@ -245,16 +242,11 @@ public void TestCommaSeparators4() } } } - N(SyntaxKind.CommaToken); - N(SyntaxKind.VariableDeclarator); - { - N(SyntaxKind.IdentifierToken, "i"); - } } M(SyntaxKind.SemicolonToken); - M(SyntaxKind.IdentifierName); + N(SyntaxKind.IdentifierName); { - M(SyntaxKind.IdentifierToken); + N(SyntaxKind.IdentifierToken, "i"); } M(SyntaxKind.SemicolonToken); N(SyntaxKind.CloseParenToken); @@ -448,4 +440,4461 @@ public void TestVariableDeclaratorVersusCondition1() } EOF(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers1() + { + UsingStatement(""" + for (int offset = 0, c1, c2; offset < length;) + { + } + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c2"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers2() + { + UsingStatement(""" + for (int offset = 0, c1 = 1, c2; offset < length;) + { + } + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c2"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers3() + { + UsingStatement(""" + for (int offset = 0, c1, c2 = 1; offset < length;) + { + } + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c2"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers4() + { + UsingStatement(""" + for (int offset = 0, c1,; offset < length;) + { + } + """, + // (1,25): error CS1001: Identifier expected + // for (int offset = 0, c1,; offset < length;) + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 25)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers5() + { + UsingStatement(""" + for (int offset = 0, c1, c2,; offset < length;) + { + } + """, + // (1,29): error CS1001: Identifier expected + // for (int offset = 0, c1, c2,; offset < length;) + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 29)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c2"); + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers6() + { + UsingStatement(""" + for (int offset = 0, c1 = ,; offset < length;) + { + } + """, + // (1,27): error CS1525: Invalid expression term ',' + // for (int offset = 0, c1 = ,; offset < length;) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ",").WithArguments(",").WithLocation(1, 27), + // (1,28): error CS1001: Identifier expected + // for (int offset = 0, c1 = ,; offset < length;) + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 28)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithInitializers7() + { + UsingStatement(""" + for (int offset = 0, c1 = , c2; offset < length;) + { + } + """, + // (1,27): error CS1525: Invalid expression term ',' + // for (int offset = 0, c1 = , c2; offset < length;) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ",").WithArguments(",").WithLocation(1, 27)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "offset"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c1"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c2"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.LessThanExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "offset"); + } + N(SyntaxKind.LessThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "length"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77160")] + public void TestMultipleDeclaratorsWithExpression1() + { + UsingStatement(""" + for (Console.WriteLine("Blah"); true;) + { + } + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Console"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "WriteLine"); + } + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.StringLiteralExpression); + { + N(SyntaxKind.StringLiteralToken, "\"Blah\""); + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer1() + { + UsingStatement(""" + for (MyType m = new() { A = 1,; true; m++) + """, + // (1,31): error CS1525: Invalid expression term ';' + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 31), + // (1,31): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 31), + // (1,37): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 37), + // (1,42): error CS1513: } expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 42), + // (1,42): error CS1002: ; expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 42), + // (1,42): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 42), + // (1,42): error CS1002: ; expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 42), + // (1,43): error CS1733: Expected expression + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 43), + // (1,43): error CS1002: ; expected + // for (MyType m = new() { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 43)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer2() + { + UsingStatement(""" + for (MyType m = new() { A = 1, B; true; m++) + """, + // (1,33): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 33), + // (1,39): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 39), + // (1,44): error CS1513: } expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 44), + // (1,44): error CS1002: ; expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 44), + // (1,44): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 44), + // (1,44): error CS1002: ; expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 44), + // (1,45): error CS1733: Expected expression + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 45), + // (1,45): error CS1002: ; expected + // for (MyType m = new() { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 45)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer3() + { + UsingStatement(""" + for (MyType m = new() { A = 1, B, ; true; m++) + """, + // (1,35): error CS1525: Invalid expression term ';' + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 35), + // (1,35): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 35), + // (1,41): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 41), + // (1,46): error CS1513: } expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 46), + // (1,46): error CS1002: ; expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 46), + // (1,46): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 46), + // (1,46): error CS1002: ; expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 46), + // (1,47): error CS1733: Expected expression + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 47), + // (1,47): error CS1002: ; expected + // for (MyType m = new() { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 47)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer4() + { + UsingStatement(""" + for (MyType m = new() { A = 1, B = ; true; m++) + """, + // (1,36): error CS1525: Invalid expression term ';' + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 36), + // (1,36): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 36), + // (1,42): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 42), + // (1,47): error CS1513: } expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 47), + // (1,47): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 47), + // (1,47): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 47), + // (1,47): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 47), + // (1,48): error CS1733: Expected expression + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 48), + // (1,48): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 48)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer5() + { + UsingStatement(""" + for (MyType m = new() { A = 1, B = ,; true; m++) + """, + // (1,36): error CS1525: Invalid expression term ',' + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ",").WithArguments(",").WithLocation(1, 36), + // (1,37): error CS1525: Invalid expression term ';' + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 37), + // (1,37): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 37), + // (1,43): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 43), + // (1,48): error CS1513: } expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 48), + // (1,48): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 48), + // (1,48): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 48), + // (1,48): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 48), + // (1,49): error CS1733: Expected expression + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 49), + // (1,49): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 49)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteInitializer6() + { + UsingStatement(""" + for (MyType m = new() { A = 1, B = 1,; true; m++) + """, + // (1,38): error CS1525: Invalid expression term ';' + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 38), + // (1,38): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 38), + // (1,44): error CS1003: Syntax error, ',' expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",").WithLocation(1, 44), + // (1,49): error CS1513: } expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(1, 49), + // (1,49): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 49), + // (1,49): error CS1525: Invalid expression term ')' + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ")").WithArguments(")").WithLocation(1, 49), + // (1,49): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(1, 49), + // (1,50): error CS1733: Expected expression + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 50), + // (1,50): error CS1002: ; expected + // for (MyType m = new() { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 50)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + [Fact] + public void TestIncompleteWith1() + { + UsingStatement(""" + for (MyType m = x with { A = 1,; true; m++) + """, + // (1,32): error CS1513: } expected + // for (MyType m = x with { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 32), + // (1,44): error CS1733: Expected expression + // for (MyType m = x with { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 44), + // (1,44): error CS1002: ; expected + // for (MyType m = x with { A = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 44)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteWith2() + { + UsingStatement(""" + for (MyType m = x with { A = 1, B; true; m++) + """, + // (1,34): error CS1513: } expected + // for (MyType m = x with { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 34), + // (1,46): error CS1733: Expected expression + // for (MyType m = x with { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 46), + // (1,46): error CS1002: ; expected + // for (MyType m = x with { A = 1, B; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 46)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteWith3() + { + UsingStatement(""" + for (MyType m = x with { A = 1, B, ; true; m++) + """, + // (1,36): error CS1513: } expected + // for (MyType m = x with { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 36), + // (1,48): error CS1733: Expected expression + // for (MyType m = x with { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 48), + // (1,48): error CS1002: ; expected + // for (MyType m = x with { A = 1, B, ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 48)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteWith4() + { + UsingStatement(""" + for (MyType m = x with { A = 1, B = ; true; m++) + """, + // (1,37): error CS1525: Invalid expression term ';' + // for (MyType m = x with { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 37), + // (1,37): error CS1513: } expected + // for (MyType m = x with { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 37), + // (1,49): error CS1733: Expected expression + // for (MyType m = x with { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 49), + // (1,49): error CS1002: ; expected + // for (MyType m = x with { A = 1, B = ; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 49)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteWith5() + { + UsingStatement(""" + for (MyType m = x with { A = 1, B = ,; true; m++) + """, + // (1,37): error CS1525: Invalid expression term ',' + // for (MyType m = x with { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ",").WithArguments(",").WithLocation(1, 37), + // (1,38): error CS1513: } expected + // for (MyType m = x with { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 38), + // (1,50): error CS1733: Expected expression + // for (MyType m = x with { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 50), + // (1,50): error CS1002: ; expected + // for (MyType m = x with { A = 1, B = ,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 50)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestIncompleteWith6() + { + UsingStatement(""" + for (MyType m = x with { A = 1, B = 1,; true; m++) + """, + // (1,39): error CS1513: } expected + // for (MyType m = x with { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 39), + // (1,51): error CS1733: Expected expression + // for (MyType m = x with { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 51), + // (1,51): error CS1002: ; expected + // for (MyType m = x with { A = 1, B = 1,; true; m++) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 51)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "MyType"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "m"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.CloseBraceToken); + } + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "m"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + M(SyntaxKind.ExpressionStatement); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_AnonymousFunction() + { + UsingStatement(""" + for (delegate() {};delegate() {};delegate() {}); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.AnonymousMethodExpression); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.AnonymousMethodExpression); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.AnonymousMethodExpression); + { + N(SyntaxKind.DelegateKeyword); + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_AnonymousObjectCreation() + { + UsingStatement(""" + for (new();new();new()); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ImplicitObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ArrayCreation() + { + UsingStatement(""" + for (new int[] { };new int[] { };new int[] { }); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.ArrayType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.ArrayRankSpecifier); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.OmittedArraySizeExpression); + { + N(SyntaxKind.OmittedArraySizeExpressionToken); + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Assignment1() + { + UsingStatement(""" + for (a=1;a=1;a=1); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SimpleAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Assignment2() + { + UsingStatement(""" + for (a+=1;a+=1;a+=1); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.AddAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusEqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.AddAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusEqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.AddAssignmentExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusEqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Cast() + { + UsingStatement(""" + for ((int)0;(int)0;(int)0); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CastExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Checked() + { + UsingStatement(""" + for (checked(0);checked(0);checked(0)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CheckedExpression); + { + N(SyntaxKind.CheckedKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CheckedExpression); + { + N(SyntaxKind.CheckedKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CheckedExpression); + { + N(SyntaxKind.CheckedKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Collection() + { + UsingStatement(""" + for ([];[];[]); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CollectionExpression); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ConditionalAccess() + { + UsingStatement(""" + for (a?.b;a?.b;a?.b); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.MemberBindingExpression); + { + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.MemberBindingExpression); + { + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ConditionalAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.QuestionToken); + N(SyntaxKind.MemberBindingExpression); + { + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_DefaultExpression1() + { + UsingStatement(""" + for (default(int);default(int);default(int)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.DefaultExpression); + { + N(SyntaxKind.DefaultKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_DefaultExpression2() + { + UsingStatement(""" + for (default;default;default); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.DefaultLiteralExpression); + { + N(SyntaxKind.DefaultKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.DefaultLiteralExpression); + { + N(SyntaxKind.DefaultKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.DefaultLiteralExpression); + { + N(SyntaxKind.DefaultKeyword); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ElementAccess() + { + UsingStatement(""" + for (a[0];a[0];a[0]); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ElementAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.BracketedArgumentList); + { + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.CloseBracketToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ImplicitArrayCreation() + { + UsingStatement(""" + for (new[]{};new[]{};new[]{}); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ImplicitArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ImplicitArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ImplicitArrayCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.OpenBracketToken); + N(SyntaxKind.CloseBracketToken); + N(SyntaxKind.ArrayInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_InterpolatedString() + { + UsingStatement(""" + for ($"";$"";$""); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.InterpolatedStringExpression); + { + N(SyntaxKind.InterpolatedStringStartToken); + N(SyntaxKind.InterpolatedStringEndToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.InterpolatedStringExpression); + { + N(SyntaxKind.InterpolatedStringStartToken); + N(SyntaxKind.InterpolatedStringEndToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.InterpolatedStringExpression); + { + N(SyntaxKind.InterpolatedStringStartToken); + N(SyntaxKind.InterpolatedStringEndToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Invocation() + { + UsingStatement(""" + for (a();a();a()); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.InvocationExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_IsPattern() + { + UsingStatement(""" + for (a is B b;a is B b;a is B b); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.DeclarationPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.DeclarationPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.DeclarationPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Literal() + { + UsingStatement(""" + for (true;true;true); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TrueLiteralExpression); + { + N(SyntaxKind.TrueKeyword); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_MemberAccess() + { + UsingStatement(""" + for (a.b;a.b;a.b); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Parenthesized() + { + UsingStatement(""" + for ((a);(a);(a)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ParenthesizedExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Postfix() + { + UsingStatement(""" + for (a++;a++;a++); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PostIncrementExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.PlusPlusToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ObjectCreation1() + { + UsingStatement(""" + for (new A();new A();new A()); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ObjectCreation2() + { + UsingStatement(""" + for (new A() { };new A() { };new A() { }); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ArgumentList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_ObjectCreation3() + { + UsingStatement(""" + for (new A { };new A { };new A { }); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ObjectCreationExpression); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.ObjectInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Prefix() + { + UsingStatement(""" + for (++a;++a;++a); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PreIncrementExpression); + { + N(SyntaxKind.PlusPlusToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PreIncrementExpression); + { + N(SyntaxKind.PlusPlusToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.PreIncrementExpression); + { + N(SyntaxKind.PlusPlusToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Query() + { + UsingStatement(""" + for (from a in b select c;from a in b select c;from a in b select c); + """, + // (1,1): error CS1073: Unexpected token 'from' + // for (from a in b select c;from a in b select c;from a in b select c); + Diagnostic(ErrorCode.ERR_UnexpectedToken, "for (from a in b select c;").WithArguments("from").WithLocation(1, 1), + // (1,1): error CS1003: Syntax error, 'foreach' expected + // for (from a in b select c;from a in b select c;from a in b select c); + Diagnostic(ErrorCode.ERR_SyntaxError, "for").WithArguments("foreach").WithLocation(1, 1), + // (1,18): error CS1026: ) expected + // for (from a in b select c;from a in b select c;from a in b select c); + Diagnostic(ErrorCode.ERR_CloseParenExpected, "select").WithLocation(1, 18)); + + N(SyntaxKind.ForEachStatement); + { + M(SyntaxKind.ForEachKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "from"); + } + N(SyntaxKind.IdentifierToken, "a"); + N(SyntaxKind.InKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + M(SyntaxKind.CloseParenToken); + N(SyntaxKind.LocalDeclarationStatement); + { + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "select"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Range1() + { + UsingStatement(""" + for (..;..;..); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Range2() + { + UsingStatement(""" + for (a..;a..;a..); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Range3() + { + UsingStatement(""" + for (..a;..a;..a); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Range4() + { + UsingStatement(""" + for (a..a;a..a;a..a); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RangeExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotDotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Ref1() + { + UsingStatement(""" + for (ref a; ref a; ref a); + """, + // (1,11): error CS1001: Identifier expected + // for (ref a; ref a; ref a); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ";").WithLocation(1, 11), + // (1,13): error CS1525: Invalid expression term 'ref' + // for (ref a; ref a; ref a); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref a").WithArguments("ref").WithLocation(1, 13), + // (1,20): error CS1525: Invalid expression term 'ref' + // for (ref a; ref a; ref a); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref a").WithArguments("ref").WithLocation(1, 20)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.RefType); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + M(SyntaxKind.VariableDeclarator); + { + M(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Ref2() + { + UsingStatement(""" + for (ref int a; ref a; ref a); + """, + // (1,17): error CS1525: Invalid expression term 'ref' + // for (ref int a; ref a; ref a); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref a").WithArguments("ref").WithLocation(1, 17), + // (1,24): error CS1525: Invalid expression term 'ref' + // for (ref int a; ref a; ref a); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref a").WithArguments("ref").WithLocation(1, 24)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.RefType); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.RefExpression); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Sizeof() + { + UsingStatement(""" + for (sizeof(a);sizeof(a);sizeof(a)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SizeOfExpression); + { + N(SyntaxKind.SizeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SizeOfExpression); + { + N(SyntaxKind.SizeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SizeOfExpression); + { + N(SyntaxKind.SizeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Switch() + { + UsingStatement(""" + for (a switch {};a switch {};a switch {}); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Throw() + { + UsingStatement(""" + for (throw a;throw a;throw a); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.ThrowExpression); + { + N(SyntaxKind.ThrowKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ThrowExpression); + { + N(SyntaxKind.ThrowKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.ThrowExpression); + { + N(SyntaxKind.ThrowKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Tuple() + { + UsingStatement(""" + for ((a, b);(a, b);(a, b)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TupleExpression); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.Argument); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_Typeof() + { + UsingStatement(""" + for (typeof(int);typeof(int);typeof(int)); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.TypeOfExpression); + { + N(SyntaxKind.TypeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TypeOfExpression); + { + N(SyntaxKind.TypeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.TypeOfExpression); + { + N(SyntaxKind.TypeOfKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_With1() + { + UsingStatement(""" + for (a with { }; a with { }; a with { }) + { + } + """, + // (1,1): error CS1073: Unexpected token ';' + // for (a with { }; a with { }; a with { }) + Diagnostic(ErrorCode.ERR_UnexpectedToken, "for (a with { }").WithArguments(";").WithLocation(1, 1), + // (1,13): error CS1002: ; expected + // for (a with { }; a with { }; a with { }) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "{").WithLocation(1, 13), + // (1,13): error CS1525: Invalid expression term '{' + // for (a with { }; a with { }; a with { }) + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "{").WithArguments("{").WithLocation(1, 13), + // (1,13): error CS1002: ; expected + // for (a with { }; a with { }; a with { }) + Diagnostic(ErrorCode.ERR_SemicolonExpected, "{").WithLocation(1, 13), + // (1,13): error CS1026: ) expected + // for (a with { }; a with { }; a with { }) + Diagnostic(ErrorCode.ERR_CloseParenExpected, "{").WithLocation(1, 13)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "with"); + } + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.SemicolonToken); + M(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact] + public void TestVariousExpressions_With2() + { + UsingStatement(""" + for (; a with { }; a with { }) + { + } + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.WithExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.WithKeyword); + N(SyntaxKind.WithInitializerExpression); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.Block); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer1() + { + UsingStatement(""" + for (;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer2() + { + UsingStatement(""" + for (int i;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "i"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer3() + { + UsingStatement(""" + for (int i, j, k;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "i"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "j"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "k"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer4() + { + UsingStatement(""" + for (int i = 0;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "i"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer5() + { + UsingStatement(""" + for (A b;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer6() + { + UsingStatement(""" + for (A b, c, d;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "d"); + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer7() + { + UsingStatement(""" + for (A b = null, c, d = null;;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "b"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "c"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "d"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer8() + { + UsingStatement(""" + for (A b = c switch { A => x, _ => y };;); + """); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "b"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.DiscardPattern); + { + N(SyntaxKind.UnderscoreToken); + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "y"); + } + } + N(SyntaxKind.CloseBraceToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer9() + { + UsingStatement(""" + for (int i =;;); + """, + // (1,13): error CS1525: Invalid expression term ';' + // for (int i =;;); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 13)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "i"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } + + [Fact] + public void TestComplexInitializer10() + { + UsingStatement(""" + for (int i = 0, j =;;); + """, + // (1,20): error CS1525: Invalid expression term ';' + // for (int i = 0, j =;;); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(1, 20)); + + N(SyntaxKind.ForStatement); + { + N(SyntaxKind.ForKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "i"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "j"); + N(SyntaxKind.EqualsValueClause); + { + N(SyntaxKind.EqualsToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + } + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.SemicolonToken); + N(SyntaxKind.CloseParenToken); + N(SyntaxKind.EmptyStatement); + { + N(SyntaxKind.SemicolonToken); + } + } + EOF(); + } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 0e8e91913e027..0528bc9c60098 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -844,7 +844,7 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( { AnalyzerDriver analyzerDriver = compilation.CreateAnalyzerDriver(analyzers, analyzerManager, severityFilter); newCompilation = compilation - .WithSemanticModelProvider(new CachingSemanticModelProvider()) + .WithSemanticModelProvider(CachingSemanticModelProvider.Instance) .WithEventQueue(new AsyncQueue()); var categorizeDiagnostics = false; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs index 573045bbf5733..4177c37847907 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CachingSemanticModelProvider.cs @@ -23,22 +23,26 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class CachingSemanticModelProvider : SemanticModelProvider { + // Provide access to CachingSemanticModelProvider through a singleton. The inner CWT is static + // to avoid leak potential -- see https://github.com/dotnet/runtime/issues/12255. + // CachingSemanticModelProvider.s_providerCache -> PerCompilationProvider -> Compilation -> CachingSemanticModelProvider + public static CachingSemanticModelProvider Instance { get; } = new CachingSemanticModelProvider(); + private static readonly ConditionalWeakTable.CreateValueCallback s_createProviderCallback = new ConditionalWeakTable.CreateValueCallback(compilation => new PerCompilationProvider(compilation)); - private readonly ConditionalWeakTable _providerCache; + private static readonly ConditionalWeakTable s_providerCache = new ConditionalWeakTable(); - public CachingSemanticModelProvider() + private CachingSemanticModelProvider() { - _providerCache = new ConditionalWeakTable(); } public override SemanticModel GetSemanticModel(SyntaxTree tree, Compilation compilation, SemanticModelOptions options = default) - => _providerCache.GetValue(compilation, s_createProviderCallback).GetSemanticModel(tree, options); + => s_providerCache.GetValue(compilation, s_createProviderCallback).GetSemanticModel(tree, options); internal void ClearCache(SyntaxTree tree, Compilation compilation) { - if (_providerCache.TryGetValue(compilation, out var provider)) + if (s_providerCache.TryGetValue(compilation, out var provider)) { provider.ClearCachedSemanticModel(tree); } @@ -46,7 +50,7 @@ internal void ClearCache(SyntaxTree tree, Compilation compilation) internal void ClearCache(Compilation compilation) { - _providerCache.Remove(compilation); + s_providerCache.Remove(compilation); } private sealed class PerCompilationProvider diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 45e7418d5887a..67c47debe0336 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -97,7 +97,7 @@ public CompilationWithAnalyzers(Compilation compilation, ImmutableArray()); _compilation = compilation; _analyzers = analyzers; @@ -723,7 +723,7 @@ private async Task ComputeAnalyzerDiagnosticsAsync(AnalysisScope? analysisScope, // subsequently discard this compilation. var compilation = analysisScope.IsSingleFileAnalysisForCompilerAnalyzer ? _compilation - : _compilation.WithSemanticModelProvider(new CachingSemanticModelProvider()).WithEventQueue(new AsyncQueue()); + : _compilation.WithSemanticModelProvider(CachingSemanticModelProvider.Instance).WithEventQueue(new AsyncQueue()); // Get the analyzer driver to execute analysis. using var driver = await CreateAndInitializeDriverAsync(compilation, _analysisOptions, analysisScope, _suppressors, categorizeDiagnostics: true, cancellationToken).ConfigureAwait(false); @@ -1188,7 +1188,7 @@ private static IEnumerable GetEffectiveDiagnosticsImpl(ImmutableArra if (compilation.SemanticModelProvider == null) { - compilation = compilation.WithSemanticModelProvider(new CachingSemanticModelProvider()); + compilation = compilation.WithSemanticModelProvider(CachingSemanticModelProvider.Instance); } var suppressMessageState = new SuppressMessageAttributeState(compilation); diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs index c1a4348299cd1..f011511041a70 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests_Regex.cs @@ -1312,4 +1312,70 @@ void Goo() Namespace("RegularExpressions"), Keyword("var")); } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/77189")] + public async Task TestStringFieldUsedLater_ProperModifiers( + TestHost testHost, + [CombinatorialValues("const", "static readonly")] string modifiers) + { + await TestAsync( + $$""" + using System.Diagnostics.CodeAnalysis; + using System.Text.RegularExpressions; + + class Program + { + private {{modifiers}} string regexValue = [|@"$(\a\t\u0020)"|]; + + void Goo() + { + Bar(regexValue); + } + + void Bar([StringSyntax(StringSyntaxAttribute.Regex)] string p) + { + } + } + """ + EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeCSharp, + testHost, + Regex.Anchor("$"), + Regex.Grouping("("), + Regex.OtherEscape("\\"), + Regex.OtherEscape("a"), + Regex.OtherEscape("\\"), + Regex.OtherEscape("t"), + Regex.OtherEscape("\\"), + Regex.OtherEscape("u"), + Regex.OtherEscape("0020"), + Regex.Grouping(")")); + } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/77189")] + public async Task TestStringFieldUsedLater_ImproperModifiers( + TestHost testHost, + [CombinatorialValues("", "static", "readonly")] string modifiers) + { + await TestAsync( + $$""" + using System.Diagnostics.CodeAnalysis; + using System.Text.RegularExpressions; + + class Program + { + private {{modifiers}} string regexValue = [|@"$(\a\t\u0020)"|]; + + void Goo() + { + Bar(regexValue); + } + + void Bar([StringSyntax(StringSyntaxAttribute.Regex)] string p) + { + } + } + """ + EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeCSharp, + testHost); + } } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 898774278f37c..199cf2081e86b 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; @@ -775,6 +776,7 @@ private static async Task TestNuGetAndVsixAnalyzerCoreAsync( if (nugetAnalyzerReferences.Count > 0) { project = project.WithAnalyzerReferences([new AnalyzerImageReference([.. nugetAnalyzerReferences])]); + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences); } var document = project.Documents.Single(); diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index f449f7978a0af..149dd06940b05 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -429,7 +430,8 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) } } - private class MockAnalyzerReference : AnalyzerReference, ICodeFixProviderFactory + private class MockAnalyzerReference + : AnalyzerReference, ICodeFixProviderFactory, SerializerService.TestAccessor.IAnalyzerReferenceWithGuid { public readonly ImmutableArray Fixers; public readonly ImmutableArray Analyzers; @@ -490,6 +492,8 @@ public override object Id } } + public Guid Guid { get; } = Guid.NewGuid(); + public override ImmutableArray GetAnalyzers(string language) => Analyzers; diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 6b141120ee330..db5836abe05f2 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Remote.Testing; +using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -435,6 +436,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project; } + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences); var applied = workspace.TryApplyChanges(project.Solution); Assert.True(applied); diff --git a/src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs b/src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs similarity index 65% rename from src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs rename to src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs index 043c8670f2733..ddc84b2b89489 100644 --- a/src/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs +++ b/src/EditorFeatures/Test/LanguageServer/VSTypeScriptHandlerTests.cs @@ -3,33 +3,39 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.Composition; using System.IO; using System.Linq; +using System.ServiceModel.Syndication; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.LanguageServer.Protocol; +using Microsoft.CommonLanguageServerProtocol.Framework; using Nerdbank.Streams; +using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using StreamJsonRpc; using Xunit; using Xunit.Abstractions; -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; +namespace Microsoft.CodeAnalysis.Editor.UnitTests.LanguageServer; + public class VSTypeScriptHandlerTests : AbstractLanguageServerProtocolTests { public VSTypeScriptHandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - protected override TestComposition Composition => base.Composition.AddParts(typeof(TypeScriptHandlerFactory)); + protected override TestComposition Composition => EditorTestCompositions.LanguageServerProtocolEditorFeatures + .AddParts(typeof(TypeScriptHandlerFactory)) + .AddParts(typeof(TestWorkspaceRegistrationService)); [Fact] public async Task TestExternalAccessTypeScriptHandlerInvoked() @@ -88,43 +94,50 @@ public async Task TestGetSimplifierOptionsOnTypeScriptDocument() Assert.Same(SimplifierOptions.CommonDefaults, simplifierOptions); } - private async Task CreateTsTestLspServerAsync(string workspaceXml, InitializationOptions? options = null) + private async Task CreateTsTestLspServerAsync(string workspaceXml, InitializationOptions? options = null) { - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var testWorkspace = CreateWorkspace(options, mutatingLspWorkspace: false, workspaceKind: null); testWorkspace.InitializeDocuments(XElement.Parse(workspaceXml), openDocuments: false); - // Ensure workspace operations are completed so we don't get unexpected workspace changes while running. - await WaitForWorkspaceOperationsAsync(testWorkspace); - var languageServerTarget = CreateLanguageServer(serverStream, serverStream, testWorkspace); - - return await TestLspServer.CreateAsync(testWorkspace, new ClientCapabilities(), languageServerTarget, clientStream); + return await VSTypeScriptTestLspServer.CreateAsync(testWorkspace, new InitializationOptions(), TestOutputLspLogger); } - private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace) + private class VSTypeScriptTestLspServer : AbstractTestLspServer { - var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var servicesProvider = workspace.ExportProvider.GetExportedValue(); - - var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter)) + public VSTypeScriptTestLspServer(LspTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions options, AbstractLspLogger logger) : base(testWorkspace, locations, options, logger) { - ExceptionStrategy = ExceptionProcessing.ISerializable, - }; - - var logger = NoOpLspLogger.Instance; + } - var languageServer = new RoslynLanguageServer( - servicesProvider, jsonRpc, messageFormatter.JsonSerializerOptions, - capabilitiesProvider, - logger, - workspace.Services.HostServices, - [InternalLanguageNames.TypeScript], - WellKnownLspServerKinds.RoslynTypeScriptLspServer); + protected override RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) + { + var capabilitiesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + var servicesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + + var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, messageFormatter)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + var languageServer = new RoslynLanguageServer( + servicesProvider, jsonRpc, messageFormatter.JsonSerializerOptions, + capabilitiesProvider, + logger, + TestWorkspace.Services.HostServices, + [InternalLanguageNames.TypeScript], + WellKnownLspServerKinds.RoslynTypeScriptLspServer); + + jsonRpc.StartListening(); + return languageServer; + } - jsonRpc.StartListening(); - return languageServer; + public static async Task CreateAsync(LspTestWorkspace testWorkspace, InitializationOptions options, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new VSTypeScriptTestLspServer(testWorkspace, locations, options, logger); + await server.InitializeAsync(); + return server; + } } internal record TSRequest(Uri Document, string Project); diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index c52637d2bcb8e..9913c17cfea6b 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -13,6 +13,7 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Diagnostics.CSharp Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Serialization Imports Microsoft.CodeAnalysis.SolutionCrawler Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics @@ -104,6 +105,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference1 = New AnalyzerImageReference(projectAnalyzers1, display:=NameOf(projectAnalyzers1)) Dim projectAnalyzerReferences1 = ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference1) project = project.WithAnalyzerReferences(projectAnalyzerReferences1) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Verify available diagnostic descriptors/analyzers descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -119,6 +121,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzers2 = ImmutableArray.Create(Of DiagnosticAnalyzer)(projectDiagnosticAnalyzer2) Dim projectAnalyzerReference2 = New AnalyzerImageReference(projectAnalyzers2, display:=NameOf(projectAnalyzers2)) project = project.AddAnalyzerReference(projectAnalyzerReference2) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Verify available diagnostic descriptors/analyzers descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -332,10 +335,12 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim p1 = solution.Projects.Single(Function(p) p.Language = LanguageNames.CSharp) p1 = p1.WithAnalyzerReferences(SpecializedCollections.SingletonCollection(New AnalyzerImageReference(ImmutableArray.Create(analyzer1)))) solution = p1.Solution + SerializerService.TestAccessor.AddAnalyzerImageReferences(p1.AnalyzerReferences) Dim p2 = solution.Projects.Single(Function(p) p.Language = LanguageNames.VisualBasic) p2 = p2.WithAnalyzerReferences(SpecializedCollections.SingletonCollection(New AnalyzerImageReference(ImmutableArray.Create(analyzer2)))) solution = p2.Solution + SerializerService.TestAccessor.AddAnalyzerImageReferences(p2.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -478,6 +483,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New ThrowsExceptionAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -512,6 +518,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockStartedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -589,6 +596,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New OperationAnalyzer(actionKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -622,6 +630,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockEndedAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -655,6 +664,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzer = New CodeBlockStartedAndEndedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -730,6 +740,7 @@ class AnonymousFunctions Dim analyzer = New CodeBlockStartedAndEndedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -765,6 +776,7 @@ class AnonymousFunctions Dim analyzer = New CompilationEndedAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -825,6 +837,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim projectDiagnostics = Await DiagnosticProviderTestUtilities.GetProjectDiagnosticsAsync(workspace, project) Assert.Equal(1, projectDiagnostics.Count()) @@ -850,6 +863,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Make couple of dummy invocations to GetDocumentDiagnostics. Dim document = project.Documents.Single() @@ -889,6 +903,7 @@ class AnonymousFunctions Dim analyzer = New StatefulCompilationAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim projectDiagnostics = Await DiagnosticProviderTestUtilities.GetProjectDiagnosticsAsync(workspace, project) @@ -924,6 +939,8 @@ class AnonymousFunctions Dim analyzer = New NamedTypeAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) + Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -962,6 +979,7 @@ class AnonymousFunctions Dim analyzer = New PartialTypeDiagnosticAnalyzer(indexOfDeclToReportDiagnostic:=1) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1017,6 +1035,7 @@ class AnonymousFunctions ' Test partial type diagnostic reported on all source files. Dim analyzerReference = New AnalyzerImageReference(analyzers) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1068,6 +1087,7 @@ public class B Dim analyzer As DiagnosticAnalyzer = New CodeBlockOrSyntaxNodeAnalyzer(isCodeBlockAnalyzer:=True) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1110,6 +1130,7 @@ public class B Dim analyzer As DiagnosticAnalyzer = New CodeBlockOrSyntaxNodeAnalyzer(isCodeBlockAnalyzer:=False) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1152,6 +1173,7 @@ public class B Dim analyzer As DiagnosticAnalyzer = New MethodSymbolAnalyzer Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1202,6 +1224,7 @@ End Class Dim analyzer = New MustOverrideMethodAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1264,6 +1287,7 @@ public class B Dim analyzer = New FieldDeclarationAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1310,6 +1334,7 @@ public class B Dim analyzer = New FieldDeclarationAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1375,6 +1400,7 @@ public class B Dim analyzer = New CompilationAnalyzerWithAnalyzerOptions() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Add additional document Dim additionalDocText = "First" @@ -1932,6 +1958,7 @@ End Class Dim analyzer = New CodeBlockActionAnalyzer(onlyStatelessAction) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -1991,6 +2018,7 @@ namespace ConsoleApplication1 ' Add analyzer Dim analyzerReference = New AnalyzerImageReference(analyzers.ToImmutableArray()) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2057,6 +2085,7 @@ class MyClass Dim analyzer = New AnalyzerWithNoSupportedDiagnostics() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2095,6 +2124,7 @@ class MyClass Dim analyzer = New CompilationAnalyzerWithSeverity(DiagnosticSeverity.Hidden, configurable:=False) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2148,6 +2178,7 @@ class C Dim analyzer = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) ' Get span to analyze Dim document = project.Documents.Single() @@ -2194,6 +2225,7 @@ class C Dim analyzer = New EnsureNoMergedNamespaceSymbolAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2238,6 +2270,7 @@ class MyClass Dim compilerAnalyzer = New CSharpCompilerDiagnosticAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(compilerAnalyzer, syntaxAnalyzer, semanticAnalyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2307,6 +2340,7 @@ class MyClass Dim compilerAnalyzer = New CSharpCompilerDiagnosticAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(compilerAnalyzer, syntaxAnalyzer, semanticAnalyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() @@ -2396,6 +2430,7 @@ public class C Dim analyzer = New AllActionsAnalyzer() Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) project = project.AddAnalyzerReference(analyzerReference) + SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() diff --git a/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs index 291cc01aced8b..bb234f12cb3f0 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/EditorTestWorkspace.cs @@ -31,12 +31,11 @@ namespace Microsoft.CodeAnalysis.Test.Utilities; -public partial class EditorTestWorkspace : TestWorkspace, ILspWorkspace +public partial class EditorTestWorkspace : TestWorkspace { private const string ReferencesOnDiskAttributeName = "ReferencesOnDisk"; private readonly Dictionary _createdTextBuffers = []; - private readonly bool _supportsLspMutation; internal EditorTestWorkspace( TestComposition? composition = null, @@ -44,8 +43,7 @@ internal EditorTestWorkspace( Guid solutionTelemetryId = default, bool disablePartialSolutions = true, bool ignoreUnchangeableDocumentsWhenApplyingChanges = true, - WorkspaceConfigurationOptions? configurationOptions = null, - bool supportsLspMutation = false) + WorkspaceConfigurationOptions? configurationOptions = null) : base(composition ?? EditorTestCompositions.EditorFeatures, workspaceKind, solutionTelemetryId, @@ -53,22 +51,6 @@ internal EditorTestWorkspace( ignoreUnchangeableDocumentsWhenApplyingChanges, configurationOptions) { - _supportsLspMutation = supportsLspMutation; - } - - bool ILspWorkspace.SupportsMutation => _supportsLspMutation; - - ValueTask ILspWorkspace.UpdateTextIfPresentAsync(DocumentId documentId, SourceText sourceText, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(_supportsLspMutation); - OnDocumentTextChanged(documentId, sourceText, PreservationMode.PreserveIdentity, requireDocumentPresent: false); - return ValueTaskFactory.CompletedTask; - } - - internal override ValueTask TryOnDocumentClosedAsync(DocumentId documentId, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(_supportsLspMutation); - return base.TryOnDocumentClosedAsync(documentId, cancellationToken); } private protected override EditorTestHostDocument CreateDocument( diff --git a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs index 60a3d2ce75ce8..f28948a0c3fbc 100644 --- a/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertToInterpolatedString/CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider.cs @@ -11,7 +11,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertToInterpolatedString; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPlaceholderToInterpolatedString), Shared] -internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider : +[method: ImportingConstructor] +[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] +internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider() : AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider< ExpressionSyntax, LiteralExpressionSyntax, @@ -21,12 +23,6 @@ internal sealed partial class CSharpConvertPlaceholderToInterpolatedStringRefact ArgumentListSyntax, InterpolationSyntax> { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider() - { - } - protected override ExpressionSyntax ParseExpression(string text) => SyntaxFactory.ParseExpression(text); } diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs index f91442f8ca011..394d5834bb4e9 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertPlaceholderToInterpolatedStringTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -15,12 +16,12 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertToInterpolatedString; [Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)] -public class ConvertPlaceholderToInterpolatedStringTests : AbstractCSharpCodeActionTest_NoEditor +public sealed class ConvertPlaceholderToInterpolatedStringTests : AbstractCSharpCodeActionTest_NoEditor { protected override CodeRefactoringProvider CreateCodeRefactoringProvider(TestWorkspace workspace, TestParameters parameters) => new CSharpConvertPlaceholderToInterpolatedStringRefactoringProvider(); - private static readonly string[] CompositeFormattedMethods = + private static readonly ImmutableArray CompositeFormattedMethods = [ "Console.Write", "Console.WriteLine", @@ -81,8 +82,7 @@ static string MakeFormattedParameters(int numberOfParameters) } } - [Theory] - [MemberData(nameof(InvocationData))] + [Theory, MemberData(nameof(InvocationData))] public async Task TestInvocationSubstitution(string before, string after) { await TestInRegularAndScriptAsync( @@ -1187,4 +1187,37 @@ void M() } """); } + + [Theory, MemberData(nameof(InvocationData))] + [WorkItem("https://github.com/dotnet/roslyn/issues/68469")] + public async Task TestInvocationSubstitution_FixAll(string before, string after) + { + await TestInRegularAndScriptAsync( + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + {|FixAllInDocument:{{before}}|}; + {{before}}; + } + } + """, + $$""" + using System; + using System.Diagnostics; + + class T + { + void M() + { + {{after}}; + {{after}}; + } + } + """); + } } diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index a4ce028eefdb4..0d8ad3bde2e12 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -24,7 +24,7 @@ internal static class SymbolCompletionItem private static readonly Action, ArrayBuilder>> s_addSymbolEncoding = AddSymbolEncoding; private static readonly Action, ArrayBuilder>> s_addSymbolInfo = AddSymbolInfo; - private static readonly char[] s_projectSeperators = [';']; + private const char ProjectSeperatorChar = ';'; private static CompletionItem CreateWorker( string displayText, @@ -236,13 +236,45 @@ private static void AddSupportedPlatforms(ArrayBuilder ProjectId.CreateFromSerialized(Guid.Parse(s))), - candidateProjects.Split(s_projectSeperators).SelectAsArray(s => ProjectId.CreateFromSerialized(Guid.Parse(s)))); + SplitIntoProjectIds(invalidProjects), + SplitIntoProjectIds(candidateProjects)); } return null; } + private static ImmutableArray SplitIntoProjectIds(string projectIds) + { + // Does the equivalent of string.Split, with fewer allocations + var start = 0; + var current = 0; + using var _ = ArrayBuilder.GetInstance(out var builder); + + while (current < projectIds.Length) + { + if (projectIds[current] == ProjectSeperatorChar) + { + if (start != current) + { + var projectGuid = Guid.Parse(projectIds.Substring(start, current - start)); + builder.Add(ProjectId.CreateFromSerialized(projectGuid)); + } + + start = current + 1; + } + + current++; + } + + if (start != current) + { + var projectGuid = Guid.Parse(projectIds.Substring(start, current - start)); + builder.Add(ProjectId.CreateFromSerialized(projectGuid)); + } + + return builder.ToImmutableAndClear(); + } + public static int GetContextPosition(CompletionItem item) { if (item.TryGetProperty("ContextPosition", out var text) && diff --git a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs index 6f6906bec80e7..45f15e9dc395f 100644 --- a/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertToInterpolatedString/AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider.cs @@ -9,12 +9,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ConvertToInterpolatedString; @@ -26,7 +29,7 @@ internal abstract class AbstractConvertPlaceholderToInterpolatedStringRefactorin TInterpolatedStringExpressionSyntax, TArgumentSyntax, TArgumentListExpressionSyntax, - TInterpolationSyntax> : CodeRefactoringProvider + TInterpolationSyntax> : SyntaxEditorBasedCodeRefactoringProvider where TExpressionSyntax : SyntaxNode where TLiteralExpressionSyntax : TExpressionSyntax where TInvocationExpressionSyntax : TExpressionSyntax @@ -35,29 +38,32 @@ internal abstract class AbstractConvertPlaceholderToInterpolatedStringRefactorin where TArgumentListExpressionSyntax : SyntaxNode where TInterpolationSyntax : SyntaxNode { - protected abstract TExpressionSyntax ParseExpression(string text); + private readonly record struct InvocationData( + TInvocationExpressionSyntax Invocation, + TArgumentSyntax PlaceholderArgument, + IMethodSymbol InvocationSymbol, + TInterpolatedStringExpressionSyntax InterpolatedString, + bool ShouldReplaceInvocation); - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var (document, span, cancellationToken) = context; - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + protected abstract TExpressionSyntax ParseExpression(string text); - var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String); - if (stringType.IsErrorType()) - return; + protected override ImmutableArray SupportedFixAllScopes { get; } = AllFixAllScopes; + private InvocationData? AnalyzeInvocation( + Document document, + SemanticModel semanticModel, + TInvocationExpressionSyntax invocation, + TArgumentSyntax placeholderArgument, + CancellationToken cancellationToken) + { var syntaxFacts = document.GetRequiredLanguageService(); - var (invocation, placeholderArgument) = await TryFindInvocationAsync().ConfigureAwait(false); - if (invocation is null || placeholderArgument is null) - return; - var placeholderExpression = syntaxFacts.GetExpressionOfArgument(placeholderArgument); var stringToken = placeholderExpression.GetFirstToken(); // don't offer if the string argument has errors in it, or if converting it to an interpolated string creates errors. if (stringToken.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) - return; + return null; // Not supported if there are any omitted arguments following the placeholder. var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); @@ -67,14 +73,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte { var argument = arguments[i]; if (syntaxFacts.GetExpressionOfArgument(argument) is null) - return; + return null; if (syntaxFacts.GetRefKindOfArgument(argument) != RefKind.None) - return; + return null; } if (semanticModel.GetSymbolInfo(invocation, cancellationToken).GetAnySymbol() is not IMethodSymbol invocationSymbol) - return; + return null; // If the user is actually passing an array to a params argument, we can't change this to be an interpolated string. if (invocationSymbol.IsParams()) @@ -82,31 +88,52 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var lastArgument = syntaxFacts.GetArgumentsOfInvocationExpression(invocation).Last(); var lastArgumentType = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(lastArgument), cancellationToken).Type; if (lastArgumentType is IArrayTypeSymbol) - return; + return null; } // if the user is explicitly passing in a CultureInfo, don't offer as it's likely they want specialized // formatting for the values. foreach (var argument in arguments) { - var type = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(argument)).Type; + var type = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfArgument(argument), cancellationToken).Type; if (type is { Name: nameof(CultureInfo), ContainingNamespace.Name: nameof(System.Globalization), ContainingNamespace.ContainingNamespace.Name: nameof(System) }) - return; + return null; } if (ParseExpression("$" + stringToken.Text) is not TInterpolatedStringExpressionSyntax interpolatedString) - return; + return null; if (interpolatedString.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) - return; + return null; var shouldReplaceInvocation = invocationSymbol is { ContainingType.SpecialType: SpecialType.System_String, Name: nameof(string.Format) }; + return new(invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation); + } + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var (document, span, cancellationToken) = context; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String); + if (stringType.IsErrorType()) + return; + + var syntaxFacts = document.GetRequiredLanguageService(); + + var (invocation, placeholderArgument) = await TryFindInvocationAsync().ConfigureAwait(false); + if (invocation is null || placeholderArgument is null) + return; + + var data = this.AnalyzeInvocation(document, semanticModel, invocation, placeholderArgument, cancellationToken); + if (data is null) + return; + context.RegisterRefactoring( CodeAction.Create( FeaturesResources.Convert_to_interpolated_string, - cancellationToken => CreateInterpolatedStringAsync( - document, invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation, cancellationToken), + cancellationToken => CreateInterpolatedStringAsync(document, data.Value, cancellationToken), nameof(FeaturesResources.Convert_to_interpolated_string)), invocation.Span); @@ -118,7 +145,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var invocations = await document.GetRelevantNodesAsync(span, cancellationToken).ConfigureAwait(false); foreach (var invocation in invocations) { - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } @@ -127,7 +154,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // User selected a single argument of the invocation (expression / format string) instead of the whole invocation. var selectedArgument = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); var invocation = selectedArgument?.Parent?.Parent as TInvocationExpressionSyntax; - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } @@ -136,47 +163,52 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // User selected the whole argument list: string format with placeholders plus all expressions var argumentList = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); var invocation = argumentList?.Parent as TInvocationExpressionSyntax; - var placeholderArgument = FindValidPlaceholderArgument(invocation); + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); if (placeholderArgument != null) return (invocation, placeholderArgument); } return default; } + } - TArgumentSyntax? FindValidPlaceholderArgument(TInvocationExpressionSyntax? invocation) + private static TArgumentSyntax? FindValidPlaceholderArgument( + ISyntaxFacts syntaxFacts, TInvocationExpressionSyntax? invocation, CancellationToken cancellationToken) + { + if (invocation != null) { - if (invocation != null) + // look for a string argument containing `"...{0}..."`, followed by more arguments. + var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); + for (int i = 0, n = arguments.Count - 1; i < n; i++) { - // look for a string argument containing `"...{0}..."`, followed by more arguments. - var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); - for (int i = 0, n = arguments.Count - 1; i < n; i++) + cancellationToken.ThrowIfCancellationRequested(); + + var argument = arguments[i]; + var expression = syntaxFacts.GetExpressionOfArgument(argument); + if (syntaxFacts.IsStringLiteralExpression(expression)) { - var argument = arguments[i]; - var expression = syntaxFacts.GetExpressionOfArgument(argument); - if (syntaxFacts.IsStringLiteralExpression(expression)) + var remainingArgCount = arguments.Count - i - 1; + Debug.Assert(remainingArgCount > 0); + var stringLiteralText = expression.GetFirstToken().Text; + if (stringLiteralText.Contains('{') && stringLiteralText.Contains('}')) { - var remainingArgCount = arguments.Count - i - 1; - Debug.Assert(remainingArgCount > 0); - var stringLiteralText = expression.GetFirstToken().Text; - if (stringLiteralText.Contains('{') && stringLiteralText.Contains('}')) - { - if (IsValidPlaceholderArgument(stringLiteralText, remainingArgCount)) - return (TArgumentSyntax)argument; - } + if (IsValidPlaceholderArgument(stringLiteralText, remainingArgCount)) + return argument; } } } - - return null; } + return null; + bool IsValidPlaceholderArgument(string stringLiteralText, int remainingArgCount) { // See how many arguments follow the `"...{0}..."`. We have to have a {0}, {1}, ... {N} part in the // string for each of them. Note, those could be in any order. for (var i = 0; i < remainingArgCount; i++) { + cancellationToken.ThrowIfCancellationRequested(); + var indexString = i.ToString(CultureInfo.InvariantCulture); if (!ContainsIndex(stringLiteralText, indexString)) return false; @@ -219,23 +251,79 @@ bool ContainsIndex(string stringLiteralText, string indexString) } } - private static async Task CreateInterpolatedStringAsync( + protected override async Task FixAllAsync( Document document, - TInvocationExpressionSyntax invocation, - TArgumentSyntax placeholderArgument, - IMethodSymbol invocationSymbol, - TInterpolatedStringExpressionSyntax interpolatedString, - bool shouldReplaceInvocation, + ImmutableArray fixAllSpans, + SyntaxEditor editor, + string? equivalenceKey, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService(); + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var normalizedSpans = new NormalizedTextSpanCollection(fixAllSpans); + + using var _ = ArrayBuilder.GetInstance(out var stack); + stack.Push(root); + + while (stack.TryPop(out var node)) + { + if (node is TInvocationExpressionSyntax invocation) + { + var placeholderArgument = FindValidPlaceholderArgument(syntaxFacts, invocation, cancellationToken); + if (placeholderArgument is not null && + AnalyzeInvocation(document, semanticModel, invocation, placeholderArgument, cancellationToken) is { } invocationData) + { + ReplaceInvocation(editor, semanticModel, syntaxFacts, invocationData, cancellationToken); + continue; + } + } + + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.IsNode) + { + var childNode = child.AsNode(); + if (normalizedSpans.IntersectsWith(childNode!.Span)) + stack.Push(childNode); + } + } + } + } + + private static async Task CreateInterpolatedStringAsync( + Document document, + InvocationData invocationData, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var syntaxGenerator = document.GetRequiredLanguageService(); + var editor = new SyntaxEditor(root, syntaxGenerator); + + ReplaceInvocation(editor, semanticModel, syntaxFacts, invocationData, cancellationToken); + + return document.WithSyntaxRoot(editor.GetChangedRoot()); + } + + private static void ReplaceInvocation( + SyntaxEditor editor, + SemanticModel semanticModel, + ISyntaxFacts syntaxFacts, + InvocationData invocationData, + CancellationToken cancellationToken) + { + var (invocation, placeholderArgument, invocationSymbol, interpolatedString, shouldReplaceInvocation) = invocationData; + var arguments = (SeparatedSyntaxList)syntaxFacts.GetArgumentsOfInvocationExpression(invocation); var literalExpression = (TLiteralExpressionSyntax?)syntaxFacts.GetExpressionOfArgument(placeholderArgument); Contract.ThrowIfNull(literalExpression); - var syntaxGenerator = document.GetRequiredLanguageService(); + var syntaxGenerator = editor.Generator; var newInterpolatedString = InsertArgumentsIntoInterpolatedString( @@ -248,9 +336,8 @@ private static async Task CreateInterpolatedStringAsync( syntaxFacts.GetExpressionOfInvocationExpression(invocation), newInterpolatedString); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = root.ReplaceNode(invocation, replacementNode.WithTriviaFrom(invocation)); - return document.WithSyntaxRoot(newRoot); + editor.ReplaceNode(invocation, replacementNode.WithTriviaFrom(invocation)); + return; ImmutableArray GetReorderedArgumentsAfterPlaceholderArgument() { diff --git a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs index 5c4048ac1afbf..503a4fdd866b5 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs @@ -20,7 +20,7 @@ internal abstract class AbstractLanguageDetector( where TOptions : struct, Enum { protected readonly EmbeddedLanguageInfo Info = info; - protected readonly EmbeddedLanguageDetector Detector = new EmbeddedLanguageDetector(info, languageIdentifiers, commentDetector); + protected readonly EmbeddedLanguageDetector Detector = new(info, languageIdentifiers, commentDetector); /// /// Whether or not this is an argument to a well known api for this language (like Regex.Match or JToken.Parse). diff --git a/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs b/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs index 40607160ecfcb..ec8859bb31ff3 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/EmbeddedLanguageDetector.cs @@ -322,7 +322,8 @@ private bool IsEmbeddedLanguageStringLiteralToken( semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken) ?? semanticModel.GetDeclaredSymbol(syntaxFacts.GetIdentifierOfVariableDeclarator(variableDeclarator).GetRequiredParent(), cancellationToken); - return IsLocalConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier); + return IsLocalConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier) || + IsFieldConsumedByApiWithStringSyntaxAttribute(symbol, container, semanticModel, cancellationToken, out identifier); } return false; @@ -336,46 +337,83 @@ private bool IsLocalConsumedByApiWithStringSyntaxAttribute( [NotNullWhen(true)] out string? identifier) { identifier = null; - if (symbol is not ILocalSymbol localSymbol) + if (symbol is not ILocalSymbol { Name: not "" } localSymbol) return false; var blockFacts = this.Info.BlockFacts; - var syntaxFacts = this.Info.SyntaxFacts; var block = tokenParent.AncestorsAndSelf().FirstOrDefault(blockFacts.IsExecutableBlock); if (block is null) return false; - var localName = localSymbol.Name; - if (localName == "") - return false; - // Now look at the next statements that follow for usages of this local variable. foreach (var statement in blockFacts.GetExecutableBlockStatements(block)) { - foreach (var descendent in statement.DescendantNodesAndSelf()) - { - cancellationToken.ThrowIfCancellationRequested(); + if (CheckDescendants(localSymbol, semanticModel, statement, cancellationToken, out identifier)) + return true; + } - if (!syntaxFacts.IsIdentifierName(descendent)) - continue; + return false; + } - var identifierToken = syntaxFacts.GetIdentifierOfIdentifierName(descendent); - if (identifierToken.ValueText != localName) - continue; + private bool IsFieldConsumedByApiWithStringSyntaxAttribute( + ISymbol? symbol, + SyntaxNode tokenParent, + SemanticModel semanticModel, + CancellationToken cancellationToken, + [NotNullWhen(true)] out string? identifier) + { + identifier = null; + if (symbol is not IFieldSymbol { Name: not "" } fieldSymbol) + return false; - var otherSymbol = semanticModel.GetSymbolInfo(descendent, cancellationToken).GetAnySymbol(); + var isConst = fieldSymbol.IsConst; + var isStaticReadonly = fieldSymbol.IsStatic && fieldSymbol.IsReadOnly; + if (!isConst && !isStaticReadonly) + return false; - // Only do a direct check here. We don't want to continually do indirect checks where a string literal - // is assigned to one local, assigned to another local, assigned to another local, and so on. - if (localSymbol.Equals(otherSymbol) && - IsEmbeddedLanguageStringLiteralToken_Direct(identifierToken, semanticModel, cancellationToken, out identifier)) - { - return true; - } + var syntaxFacts = this.Info.SyntaxFacts; + + var typeDeclaration = tokenParent.AncestorsAndSelf().FirstOrDefault(syntaxFacts.IsTypeDeclaration); + if (typeDeclaration is null) + return false; + + return CheckDescendants(fieldSymbol, semanticModel, typeDeclaration, cancellationToken, out identifier); + } + + private bool CheckDescendants( + ISymbol symbol, + SemanticModel semanticModel, + SyntaxNode node, + CancellationToken cancellationToken, + [NotNullWhen(true)] out string? identifier) + { + var symbolName = symbol.Name; + var syntaxFacts = this.Info.SyntaxFacts; + + foreach (var descendent in node.DescendantNodesAndSelf()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!syntaxFacts.IsIdentifierName(descendent)) + continue; + + var identifierToken = syntaxFacts.GetIdentifierOfIdentifierName(descendent); + if (identifierToken.ValueText != symbolName) + continue; + + var otherSymbol = semanticModel.GetSymbolInfo(descendent, cancellationToken).GetAnySymbol(); + + // Only do a direct check here. We don't want to continually do indirect checks where a string literal + // is assigned to one local, assigned to another local, assigned to another local, and so on. + if (symbol.Equals(otherSymbol) && + IsEmbeddedLanguageStringLiteralToken_Direct(identifierToken, semanticModel, cancellationToken, out identifier)) + { + return true; } } + identifier = null; return false; } diff --git a/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb b/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb index ed51d59f2626f..07ab30c4f4720 100644 --- a/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb +++ b/src/Features/Lsif/GeneratorTest/FoldingRangeTests.vb @@ -2,11 +2,9 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Test.Utilities Imports Roslyn.LanguageServer.Protocol Imports Roslyn.Test.Utilities -Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests @@ -52,7 +50,6 @@ using System.Linq;|}", "...")> , openDocuments:=False, composition:=TestLsifOutput.TestComposition) - Dim annotatedLocations = Await AbstractLanguageServerProtocolTests.GetAnnotatedLocationsAsync(workspace, workspace.CurrentSolution) Dim expectedRanges = annotatedLocations.SelectMany(Function(kvp) kvp.Value.Select(Function(location) CreateFoldingRange(kvp.Key, location.Range, collapsedText))).OrderByDescending(Function(range) range.StartLine).ToArray() diff --git a/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj b/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj index 0a9d98d4bcde6..ac2f33f5aabe0 100644 --- a/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj +++ b/src/Features/Lsif/GeneratorTest/Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.vbproj @@ -10,6 +10,7 @@ + diff --git a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj index 392d25293108e..766f43532bf19 100644 --- a/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj +++ b/src/Features/TestUtilities/Microsoft.CodeAnalysis.Features.Test.Utilities.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb index 8ef9b016e58ea..0bbc2ea490efe 100644 --- a/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/ConvertToInterpolatedString/VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider.vb @@ -10,7 +10,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertToInterpolatedString - Partial Friend Class VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider + Partial Friend NotInheritable Class VisualBasicConvertPlaceholderToInterpolatedStringRefactoringProvider Inherits AbstractConvertPlaceholderToInterpolatedStringRefactoringProvider(Of ExpressionSyntax, LiteralExpressionSyntax, diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs similarity index 100% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs similarity index 83% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 29071e6c24179..949301098af8b 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -15,16 +15,12 @@ using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Test; -using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.CodeActions; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; -using Microsoft.CodeAnalysis.LanguageServer.UnitTests; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -51,10 +47,6 @@ protected AbstractLanguageServerProtocolTests(ITestOutputHelper? testOutputHelpe TestOutputLspLogger = testOutputHelper != null ? new TestOutputLspLogger(testOutputHelper) : NoOpLspLogger.Instance; } - protected static readonly TestComposition EditorFeaturesLspComposition = EditorTestCompositions.LanguageServerProtocolEditorFeatures - .AddParts(typeof(TestDocumentTrackingService)) - .AddParts(typeof(TestWorkspaceRegistrationService)); - protected static readonly TestComposition FeaturesLspComposition = LspTestCompositions.LanguageServerProtocol .AddParts(typeof(TestDocumentTrackingService)) .AddParts(typeof(TestWorkspaceRegistrationService)); @@ -108,7 +100,7 @@ private protected class OrderLocations : Comparer public override int Compare(LSP.Location? x, LSP.Location? y) => CompareLocations(x, y); } - protected virtual TestComposition Composition => EditorFeaturesLspComposition; + protected virtual TestComposition Composition => FeaturesLspComposition; private protected virtual TestAnalyzerReferenceByLanguage CreateTestAnalyzersReference() => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); @@ -180,7 +172,7 @@ private protected static string ApplyTextEdits(LSP.TextEdit[]? edits, SourceText internal static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kind, string name, LSP.Location location, Glyph glyph, string? containerName = null) { - var imageId = glyph.GetImageId(); + var (guid, id) = glyph.GetVsImageData(); #pragma warning disable CS0618 // SymbolInformation is obsolete, need to switch to DocumentSymbol/WorkspaceSymbol var info = new LSP.VSSymbolInformation() @@ -188,7 +180,7 @@ internal static LSP.SymbolInformation CreateSymbolInformation(LSP.SymbolKind kin Kind = kind, Name = name, Location = location, - Icon = new LSP.VSImageId { Guid = imageId.Guid, Id = imageId.Id }, + Icon = new LSP.VSImageId { Guid = guid, Id = id }, }; if (containerName != null) @@ -279,7 +271,7 @@ private protected static LSP.CompletionParams CreateCompletionParams( }; if (tags != null) - item.Icon = tags.ToImmutableArray().GetFirstGlyph().GetImageElement().ToLSPImageElement(); + item.Icon = new(tags.ToImmutableArray().GetFirstGlyph().ToLSPImageId()); if (commitCharacters != null) item.CommitCharacters = [.. commitCharacters.Value.Select(c => c.ToString())]; @@ -321,13 +313,13 @@ private protected Task CreateTestLspServerAsync( var workspace = CreateWorkspace(lspOptions, workspaceKind: null, mutatingLspWorkspace, composition); workspace.InitializeDocuments( - TestWorkspace.CreateWorkspaceElement(languageName, files: markups, fileContainingFolders: lspOptions.DocumentFileContainingFolders, sourceGeneratedFiles: lspOptions.SourceGeneratedMarkups, commonReferences: commonReferences), + LspTestWorkspace.CreateWorkspaceElement(languageName, files: markups, fileContainingFolders: lspOptions.DocumentFileContainingFolders, sourceGeneratedFiles: lspOptions.SourceGeneratedMarkups, commonReferences: commonReferences), openDocuments: false); return CreateTestLspServerAsync(workspace, lspOptions, languageName); } - private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace, InitializationOptions initializationOptions, string languageName) + private async Task CreateTestLspServerAsync(LspTestWorkspace workspace, InitializationOptions initializationOptions, string languageName) { var solution = workspace.CurrentSolution; @@ -374,10 +366,10 @@ private protected async Task CreateXmlTestLspServerAsync( return await TestLspServer.CreateAsync(workspace, lspOptions, TestOutputLspLogger); } - internal EditorTestWorkspace CreateWorkspace( + internal LspTestWorkspace CreateWorkspace( InitializationOptions? options, string? workspaceKind, bool mutatingLspWorkspace, TestComposition? composition = null) { - var workspace = new EditorTestWorkspace( + var workspace = new LspTestWorkspace( composition ?? Composition, workspaceKind, configurationOptions: new WorkspaceConfigurationOptions(ValidateCompilationTrackerStates: true), supportsLspMutation: mutatingLspWorkspace); options?.OptionUpdater?.Invoke(workspace.GetService()); @@ -390,13 +382,19 @@ internal EditorTestWorkspace CreateWorkspace( /// Waits for the async operations on the workspace to complete. /// This ensures that events like workspace registration / workspace changes are processed by the time we exit this method. /// - protected static async Task WaitForWorkspaceOperationsAsync(EditorTestWorkspace workspace) + protected static async Task WaitForWorkspaceOperationsAsync(TestWorkspace workspace) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var workspaceWaiter = GetWorkspaceWaiter(workspace); await workspaceWaiter.ExpeditedWaitAsync(); } - private static IAsynchronousOperationWaiter GetWorkspaceWaiter(EditorTestWorkspace workspace) + private static IAsynchronousOperationWaiter GetWorkspaceWaiter(TestWorkspace workspace) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var operations = workspace.ExportProvider.GetExportedValue(); return operations.GetWaiter(FeatureAttribute.Workspace); @@ -419,7 +417,7 @@ protected static void AddMappedDocument(Workspace workspace, string markup) workspace.TryApplyChanges(newSolution); } - protected static async Task AddGeneratorAsync(ISourceGenerator generator, EditorTestWorkspace workspace) + protected static async Task AddGeneratorAsync(ISourceGenerator generator, LspTestWorkspace workspace) { var analyzerReference = new TestGeneratorReference(generator); @@ -433,7 +431,7 @@ protected static async Task AddGeneratorAsync(ISourceGenerato return analyzerReference; } - protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, EditorTestWorkspace workspace) + protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, LspTestWorkspace workspace) { var solution = workspace.CurrentSolution .Projects.Single() @@ -444,7 +442,10 @@ protected static async Task RemoveGeneratorAsync(AnalyzerReference reference, Ed await WaitForWorkspaceOperationsAsync(workspace); } - internal static async Task>> GetAnnotatedLocationsAsync(EditorTestWorkspace workspace, Solution solution) + internal static async Task>> GetAnnotatedLocationsAsync(TestWorkspace workspace, Solution solution) + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution { var locations = new Dictionary>(); foreach (var testDocument in workspace.Documents) @@ -531,44 +532,71 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U } }; - internal sealed class TestLspServer : IAsyncDisposable + /// + /// Implementation of + /// using the workspace. + /// + internal sealed class TestLspServer : AbstractTestLspServer { - public readonly EditorTestWorkspace TestWorkspace; - private readonly Dictionary> _locations; + public TestLspServer(LspTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions initializationOptions, AbstractLspLogger logger) + : base(testWorkspace, locations, initializationOptions, logger) + { + } + + public static async Task CreateAsync(LspTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new TestLspServer(testWorkspace, locations, initializationOptions, logger); + await server.InitializeAsync(); + return server; + } + } + + internal abstract class AbstractTestLspServer : IAsyncDisposable + where TDocument : TestHostDocument + where TProject : TestHostProject + where TSolution : TestHostSolution + where TWorkspace : TestWorkspace + { + public readonly TWorkspace TestWorkspace; private readonly JsonRpc _clientRpc; + private readonly Dictionary> _locations; private readonly ICodeAnalysisDiagnosticAnalyzerService _codeAnalysisService; - - private readonly RoslynLanguageServer LanguageServer; + private readonly InitializationOptions _initializationOptions; + private readonly Lazy _languageServer; public LSP.ClientCapabilities ClientCapabilities { get; } - private TestLspServer( - EditorTestWorkspace testWorkspace, + public AbstractTestLspServer( + TWorkspace testWorkspace, Dictionary> locations, - LSP.ClientCapabilities clientCapabilities, - RoslynLanguageServer target, - Stream clientStream, - object? clientTarget = null, - IJsonRpcMessageFormatter? clientMessageFormatter = null) + InitializationOptions initializationOptions, + AbstractLspLogger logger) { TestWorkspace = testWorkspace; - ClientCapabilities = clientCapabilities; + _initializationOptions = initializationOptions; _locations = locations; _codeAnalysisService = testWorkspace.Services.GetRequiredService(); - LanguageServer = target; + ClientCapabilities = initializationOptions.ClientCapabilities; - clientMessageFormatter ??= RoslynLanguageServer.CreateJsonMessageFormatter(); + var clientMessageFormatter = initializationOptions.ClientMessageFormatter ?? RoslynLanguageServer.CreateJsonMessageFormatter(); - _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, clientMessageFormatter), clientTarget) + var (clientStream, serverStream) = FullDuplexStream.CreatePair(); + + _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, clientMessageFormatter), initializationOptions.ClientTarget) { ExceptionStrategy = ExceptionProcessing.ISerializable, }; - // Workspace listener events do not run in tests, so we manually register the lsp misc workspace. - TestWorkspace.GetService().Register(GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); + _languageServer = new(() => + { + var server = CreateLanguageServer(serverStream, serverStream, _initializationOptions.ServerKind, logger); + + InitializeClientRpc(); + return server; + }); - InitializeClientRpc(); } private void InitializeClientRpc() @@ -579,56 +607,39 @@ private void InitializeClientRpc() Assert.False(workspaceWaiter.HasPendingWork); } - internal static async Task CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + internal async Task InitializeAsync() { // Important: We must wait for workspace creation operations to finish. // Otherwise we could have a race where workspace change events triggered by creation are changing the state // created by the initial test steps. This can interfere with the expected test state. - await WaitForWorkspaceOperationsAsync(testWorkspace); + await WaitForWorkspaceOperationsAsync(TestWorkspace); - var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); - - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - var languageServer = CreateLanguageServer(serverStream, serverStream, testWorkspace, initializationOptions.ServerKind, logger); + // Initialize the language server + _ = _languageServer.Value; - var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, languageServer, clientStream, initializationOptions.ClientTarget, initializationOptions.ClientMessageFormatter); + // Workspace listener events do not run in tests, so we manually register the lsp misc workspace. + // This must be done after the language server is created in order to access the misc workspace off of the LSP workspace manager. + TestWorkspace.GetService().Register(GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); - if (initializationOptions.CallInitialize) + if (_initializationOptions.CallInitialize) { - await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams + await this.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams { - Capabilities = initializationOptions.ClientCapabilities, - Locale = initializationOptions.Locale, + Capabilities = _initializationOptions.ClientCapabilities, + Locale = _initializationOptions.Locale, }, CancellationToken.None); } - if (initializationOptions.CallInitialized) + if (_initializationOptions.CallInitialized) { - await server.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); + await this.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); } - - return server; - } - - internal static async Task CreateAsync(EditorTestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, RoslynLanguageServer target, Stream clientStream) - { - var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); - var server = new TestLspServer(testWorkspace, locations, clientCapabilities, target, clientStream); - - await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams - { - Capabilities = clientCapabilities, - }, CancellationToken.None); - - await server.ExecuteRequestAsync(LSP.Methods.InitializedName, new LSP.InitializedParams { }, CancellationToken.None); - - return server; } - private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, EditorTestWorkspace workspace, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) + protected virtual RoslynLanguageServer CreateLanguageServer(Stream inputStream, Stream outputStream, WellKnownLspServerKinds serverKind, AbstractLspLogger logger) { - var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var factory = workspace.ExportProvider.GetExportedValue(); + var capabilitiesProvider = TestWorkspace.ExportProvider.GetExportedValue(); + var factory = TestWorkspace.ExportProvider.GetExportedValue(); var jsonMessageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)) @@ -636,7 +647,7 @@ private static RoslynLanguageServer CreateLanguageServer(Stream inputStream, Str ExceptionStrategy = ExceptionProcessing.ISerializable, }; - var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializerOptions, capabilitiesProvider, serverKind, logger, workspace.Services.HostServices); + var languageServer = (RoslynLanguageServer)factory.Create(jsonRpc, jsonMessageFormatter.JsonSerializerOptions, capabilitiesProvider, serverKind, logger, TestWorkspace.Services.HostServices); jsonRpc.StartListening(); return languageServer; @@ -698,6 +709,33 @@ public async Task OpenDocumentAsync(Uri documentUri, string? text = null, string await ExecuteRequestAsync(LSP.Methods.TextDocumentDidOpenName, didOpenParams, CancellationToken.None); } + /// + /// Opens a document in the workspace only, and waits for workspace operations. + /// Use if the document should be opened in LSP"/> + /// + public async Task OpenDocumentInWorkspaceAsync(DocumentId documentId, bool openAllLinkedDocuments, SourceText? text = null) + { + var document = TestWorkspace.CurrentSolution.GetDocument(documentId); + Contract.ThrowIfNull(document); + + text ??= await TestWorkspace.CurrentSolution.GetDocument(documentId)!.GetTextAsync(CancellationToken.None); + + List linkedDocuments = [documentId]; + if (openAllLinkedDocuments) + { + linkedDocuments.AddRange(document.GetLinkedDocumentIds()); + } + + var container = new TestStaticSourceTextContainer(text); + + foreach (var documentIdToOpen in linkedDocuments) + { + TestWorkspace.OnDocumentOpened(documentIdToOpen, container); + } + + await WaitForWorkspaceOperationsAsync(TestWorkspace); + } + public Task ReplaceTextAsync(Uri documentUri, params (LSP.Range Range, string Text)[] changes) { var didChangeParams = CreateDidChangeTextDocumentParams( @@ -741,7 +779,7 @@ public async Task ExitTestServerAsync() // of the request itself since it will throw a ConnectionLostException. // Instead we wait for the server's exit task to be completed. await _clientRpc.NotifyAsync(LSP.Methods.ExitName).ConfigureAwait(false); - await LanguageServer.WaitForExitAsync().ConfigureAwait(false); + await _languageServer.Value.WaitForExitAsync().ConfigureAwait(false); } public IList GetLocations(string locationName) => _locations[locationName]; @@ -772,15 +810,15 @@ internal async Task WaitForDiagnosticsAsync() await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync(); } - internal RequestExecutionQueue.TestAccessor? GetQueueAccessor() => LanguageServer.GetTestAccessor().GetQueueAccessor(); + internal RequestExecutionQueue.TestAccessor? GetQueueAccessor() => _languageServer.Value.GetTestAccessor().GetQueueAccessor(); internal LspWorkspaceManager.TestAccessor GetManagerAccessor() => GetRequiredLspService().GetTestAccessor(); internal LspWorkspaceManager GetManager() => GetRequiredLspService(); - internal AbstractLanguageServer.TestAccessor GetServerAccessor() => LanguageServer.GetTestAccessor(); + internal AbstractLanguageServer.TestAccessor GetServerAccessor() => _languageServer.Value.GetTestAccessor(); - internal T GetRequiredLspService() where T : class, ILspService => LanguageServer.GetTestAccessor().GetRequiredLspService(); + internal T GetRequiredLspService() where T : class, ILspService => _languageServer.Value.GetTestAccessor().GetRequiredLspService(); internal ImmutableArray GetTrackedTexts() => [.. GetManager().GetTrackedLspText().Values.Select(v => v.Text)]; @@ -794,14 +832,14 @@ public async ValueTask DisposeAsync() // Some tests will manually call shutdown and exit, so attempting to call this during dispose // will fail as the server's jsonrpc instance will be disposed of. - if (!LanguageServer.GetTestAccessor().HasShutdownStarted()) + if (!_languageServer.Value.GetTestAccessor().HasShutdownStarted()) { await ShutdownTestServerAsync(); await ExitTestServerAsync(); } // Wait for all the exit notifications to run to completion. - await LanguageServer.WaitForExitAsync(); + await _languageServer.Value.WaitForExitAsync(); TestWorkspace.Dispose(); _clientRpc.Dispose(); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs similarity index 97% rename from src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs index 30726fc3aaa9b..0b09f4041b37e 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLspBuildOnlyDiagnosticsTests.cs @@ -22,7 +22,7 @@ public abstract class AbstractLspBuildOnlyDiagnosticsTests [Fact] public void TestExportedDiagnosticIds() { - var attribute = this.LspBuildOnlyDiagnosticsType.GetCustomAttribute(); + var attribute = this.LspBuildOnlyDiagnosticsType.GetCustomAttribute()!; var actualDiagnosticCodes = attribute.BuildOnlyDiagnostics; var missing = ExpectedDiagnosticCodes.Except(actualDiagnosticCodes).OrderBy(k => k).ToList(); diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs similarity index 95% rename from src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs index c8ba5e8bdbd99..8b7dfa57e4db3 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/TestOutputLspLogger.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestOutputLspLogger.cs @@ -3,10 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CommonLanguageServerProtocol.Framework; using Xunit.Abstractions; -namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; +namespace Roslyn.Test.Utilities; internal sealed class TestOutputLspLogger : AbstractLspLogger, ILspService { diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs similarity index 50% rename from src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs rename to src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs index f83897f0c6930..db393fa2ad034 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs +++ b/src/LanguageServer/Protocol.TestUtilities/LanguageServer/TestWorkspaceRegistrationService.cs @@ -10,15 +10,9 @@ namespace Roslyn.Test.Utilities; -public abstract partial class AbstractLanguageServerProtocolTests +[Export(typeof(LspWorkspaceRegistrationService)), Shared, PartNotDiscoverable] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class TestWorkspaceRegistrationService() : LspWorkspaceRegistrationService { - [Export(typeof(LspWorkspaceRegistrationService)), Shared, PartNotDiscoverable] - internal class TestWorkspaceRegistrationService : LspWorkspaceRegistrationService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestWorkspaceRegistrationService() - { - } - } } diff --git a/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj b/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj index 910bce3e518ef..91693179de616 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj +++ b/src/LanguageServer/Protocol.TestUtilities/Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj @@ -16,5 +16,10 @@ + + + + + \ No newline at end of file diff --git a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs index 96830c508dd90..5695456e45a13 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs +++ b/src/LanguageServer/Protocol.TestUtilities/Workspaces/LspTestWorkspace.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs b/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs new file mode 100644 index 0000000000000..40b0f2bd9a904 --- /dev/null +++ b/src/LanguageServer/Protocol.TestUtilities/Workspaces/TestStaticSourceTextContainer.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Test.Utilities; + +/// +/// Various tests often need a source text container to simulate workspace OnDocumentOpened calls. +/// +internal class TestStaticSourceTextContainer(SourceText text) : SourceTextContainer +{ + public override SourceText CurrentText => text; + + public override event EventHandler TextChanged + { + add { } + remove { } + } +} diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs index 7de75d8b668ab..ef96728725dcb 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs @@ -5,13 +5,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 39cf98993dda9..52dde0ab537aa 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -42,6 +42,7 @@ internal partial class DiagnosticAnalyzerService // Make sure the cached pair was computed with at least the same state sets we're asking about. if not, // recompute and cache with the new state sets. if (!s_projectToCompilationWithAnalyzers.TryGetValue(projectState, out var tupleBox) || + tupleBox.Value.checksum != checksum || !analyzers.IsSubsetOf(tupleBox.Value.analyzers)) { var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index 849e5821bdc8f..3c52425994a08 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -64,8 +64,8 @@ private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, Pr return ProjectAnalyzerInfo.Default; } - var hostAnalyzers = solution.Analyzers; - var analyzersPerReference = hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); + var solutionAnalyzers = solution.Analyzers; + var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); if (analyzersPerReference.Count == 0) { return ProjectAnalyzerInfo.Default; @@ -78,7 +78,7 @@ private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, Pr // workspace placeholder analyzers. So we should never get host analyzers back here. Contract.ThrowIfTrue(newHostAnalyzers.Count > 0); - var skippedAnalyzersInfo = solution.Analyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); + var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo); } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 11712ea77dd66..2a46388f88f88 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -2,16 +2,12 @@ // 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.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 93490ccb00f0d..c472bbb22dbd5 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs index 115998c9c86ca..8acf17687cc4b 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionResolveTests.cs @@ -26,7 +26,7 @@ public CodeActionResolveTests(ITestOutputHelper testOutputHelper) : base(testOut { } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionResolveHandlerAsync(bool mutatingLspWorkspace) { var initialMarkup = @@ -77,7 +77,7 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionResolveHandlerAsync_NestedAction(bool mutatingLspWorkspace) { var initialMarkup = @@ -138,7 +138,7 @@ void M() AssertJsonEquals(expectedResolvedAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename(bool mutatingLspWorkspace) { var markUp = @" @@ -198,23 +198,35 @@ class {|caret:ABC|} AssertJsonEquals(expectedCodeAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestLinkedDocuments(bool mutatingLspWorkspace) { - var xmlWorkspace = @" + var originalMarkup = """ + class C + { + public static readonly int {|caret:_value|} = 10; + } + """; + var xmlWorkspace = $""" - class C -{ - public static readonly int {|caret:_value|} = 10; -} - + {originalMarkup} -"; + """; + + var expectedText = """ + class C + { + private static readonly int value = 10; + + public static int Value => value; + } + """; + await using var testLspServer = await CreateXmlTestLspServerAsync(xmlWorkspace, mutatingLspWorkspace); var titlePath = new string[] { string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value") }; var unresolvedCodeAction = CodeActionsTests.CreateCodeAction( @@ -230,78 +242,18 @@ public async Task TestLinkedDocuments(bool mutatingLspWorkspace) diagnostics: null); var actualResolvedAction = await RunGetCodeActionResolveAsync(testLspServer, unresolvedCodeAction); - var edits = new SumType[] - { - new TextEdit() - { - NewText = "private", - Range = new LSP.Range() - { - Start = new Position - { - Line = 2, - Character = 4 - }, - End = new Position - { - Line = 2, - Character = 10 - } - } - }, - new TextEdit - { - NewText = string.Empty, - Range = new LSP.Range - { - Start = new Position - { - Line = 2, - Character = 31 - }, - End = new Position - { - Line = 2, - Character = 32 - } - } - }, - new TextEdit - { - NewText = @" - public static int Value => value;", - Range = new LSP.Range - { - Start = new Position - { - Line = 2, - Character = 43 - }, - End = new Position - { - Line = 2, - Character = 43 - } - } - } - }; - var expectedCodeAction = CodeActionsTests.CreateCodeAction( - title: string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), - kind: CodeActionKind.Refactor, - children: [], - data: CreateCodeActionResolveData( - string.Format(FeaturesResources.Encapsulate_field_colon_0_and_use_property, "_value"), - testLspServer.GetLocations("caret").Single(), titlePath), - priority: VSInternalPriorityLevel.Normal, - groupName: "Roslyn2", - applicableRange: new LSP.Range { Start = new Position { Line = 2, Character = 33 }, End = new Position { Line = 39, Character = 2 } }, - diagnostics: null, - edit: GenerateWorkspaceEdit(testLspServer.GetLocations("caret"), edits)); - AssertJsonEquals(expectedCodeAction, actualResolvedAction); + AssertEx.NotNull(actualResolvedAction.Edit); + var textDocumentEdit = (LSP.TextDocumentEdit[])actualResolvedAction.Edit.DocumentChanges.Value; + Assert.Single(textDocumentEdit); + var originalText = await testLspServer.GetDocumentTextAsync(textDocumentEdit[0].TextDocument.Uri); + var edits = textDocumentEdit[0].Edits.Select(e => (LSP.TextEdit)e.Value).ToArray(); + var updatedText = ApplyTextEdits(edits, originalText); + Assert.Equal(expectedText, updatedText); + } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestMoveTypeToDifferentFile(bool mutatingLspWorkspace) { var markUp = @" @@ -423,7 +375,7 @@ class BCD AssertJsonEquals(expectedCodeAction, actualResolvedAction); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestMoveTypeToDifferentFileInDirectory(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index e62cd3c664ca5..e04e6459b4a65 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.CodeActions; public class CodeActionsTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) { - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHandlerAsync(bool mutatingLspWorkspace) { var markup = @@ -57,7 +57,7 @@ void M() AssertJsonEquals(expected, useImplicitType); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHandlerAsync_NestedAction(bool mutatingLspWorkspace) { var markup = @@ -90,13 +90,13 @@ void M() var results = await RunGetCodeActionsAsync(testLspServer, CreateCodeActionParams(caretLocation)); var topLevelAction = Assert.Single(results, action => action.Title == titlePath[0]); - var introduceConstant = topLevelAction.Children.FirstOrDefault( + var introduceConstant = topLevelAction.Children!.FirstOrDefault( r => JsonSerializer.Deserialize((JsonElement)r.Data!, ProtocolConversions.LspJsonSerializerOptions)!.UniqueIdentifier == titlePath[1]); AssertJsonEquals(expected, introduceConstant); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestCodeActionHasCorrectDiagnostics(bool mutatingLspWorkspace) { var markup = @@ -134,11 +134,11 @@ void M() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var addImport = results.FirstOrDefault(r => r.Title.Contains($"using System.Threading.Tasks")); - Assert.Equal(1, addImport.Diagnostics!.Length); + Assert.Equal(1, addImport!.Diagnostics!.Length); Assert.Equal(AddImportDiagnosticIds.CS0103, addImport.Diagnostics.Single().Code!.Value); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestNoSuppressionFixerInStandardLSP(bool mutatingLspWorkspace) { var markup = """ @@ -175,7 +175,7 @@ class ABC Assert.Equal("Make method synchronous", results[0].Title); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -204,7 +204,7 @@ private void XYZ() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); var inline = results.FirstOrDefault(r => r.Title.Contains($"Inline 'A()'")); - var data = GetCodeActionResolveData(inline); + var data = GetCodeActionResolveData(inline!); Assert.NotNull(data); // Asserts that there are NestedActions on Inline @@ -218,10 +218,10 @@ private void XYZ() Assert.Equal("Inline and keep 'A()'", nestedActionData!.CodeActionPath[1]); // Asserts that there is a Command present on an action with nested actions - Assert.NotNull(inline.Command); + Assert.NotNull(inline?.Command); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedFixAllCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -266,7 +266,7 @@ class ABC Assert.Equal("Fix All: in Source", data.NestedCodeActions!.Value[1].Title); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestStandardLspNestedResolveTopLevelCodeAction(bool mutatingLspWorkspace) { var markup = """ @@ -296,7 +296,7 @@ private void XYZ() var results = await RunGetCodeActionsAsync(testLspServer, codeActionParams); // Assert that nested code actions aren't enumerated. var inline = results.FirstOrDefault(r => r.Title.Contains($"Inline 'A()'")); - var resolvedAction = await RunGetCodeActionResolveAsync(testLspServer, inline); + var resolvedAction = await RunGetCodeActionResolveAsync(testLspServer, inline!); Assert.Null(resolvedAction.Edit); } @@ -306,7 +306,7 @@ private static async Task RunGetCodeActionsAsync( { var result = await testLspServer.ExecuteRequestAsync( LSP.Methods.TextDocumentCodeActionName, codeActionParams, CancellationToken.None); - return [.. result.Cast()]; + return [.. result!.Cast()]; } private static async Task RunGetCodeActionResolveAsync( diff --git a/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs index 80bd47e9d2207..741476e85769d 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeActions/RunCodeActionsTests.cs @@ -24,7 +24,7 @@ public RunCodeActionsTests(ITestOutputHelper testOutputHelper) : base(testOutput { } - [WpfTheory(Skip = "https://github.com/dotnet/roslyn/issues/65303"), CombinatorialData] + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/65303"), CombinatorialData] public async Task TestRunCodeActions(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs b/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs index 37ff9c71e2789..1a9156b341aeb 100644 --- a/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/CodeLens/CSharpCodeLensTests.cs @@ -386,8 +386,8 @@ void UseM() }; var actualCodeLenses = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCodeLensName, codeLensParamsDoc1, CancellationToken.None); - var firstCodeLens = actualCodeLenses.First(); - var data = JsonSerializer.Deserialize(firstCodeLens.Data!.ToString(), ProtocolConversions.LspJsonSerializerOptions); + var firstCodeLens = actualCodeLenses!.First(); + var data = JsonSerializer.Deserialize(firstCodeLens.Data!.ToString()!, ProtocolConversions.LspJsonSerializerOptions); AssertEx.NotNull(data); // Update the document so the syntax version changes @@ -412,10 +412,7 @@ void M(A a) await using var testLspServer = await CreateTestLspServerAsync(markup, lspMutatingWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableReferencesCodeLens, LanguageNames.CSharp, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableReferencesCodeLens, LanguageNames.CSharp, false) }); var actualCodeLenses = await GetCodeLensAsync(testLspServer); AssertEx.Empty(actualCodeLenses); @@ -446,10 +443,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false) }); await VerifyTestCodeLensAsync(testLspServer, FeaturesResources.Run_Test, FeaturesResources.Debug_Test); } @@ -479,10 +473,7 @@ public void M() await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, false) }); await VerifyTestCodeLensAsync(testLspServer, FeaturesResources.Run_All_Tests, FeaturesResources.Debug_All_Tests); } @@ -512,10 +503,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, true); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspUsingDevkitFeatures, true) }); await VerifyTestCodeLensMissingAsync(testLspServer); } @@ -545,10 +533,7 @@ class A await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, - OptionUpdater = (globalOptions) => - { - globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableTestsCodeLens, LanguageNames.CSharp, false); - } + OptionUpdater = (globalOptions) => globalOptions.SetGlobalOption(LspOptionsStorage.LspEnableTestsCodeLens, LanguageNames.CSharp, false) }); await VerifyTestCodeLensMissingAsync(testLspServer); } diff --git a/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs b/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs index 7b9c222145ab4..5e8094d8274c8 100644 --- a/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Commands/ExecuteWorkspaceCommandTests.cs @@ -63,7 +63,7 @@ public TestWorkspaceCommandHandler() public override TextDocumentIdentifier GetTextDocumentIdentifier(ExecuteCommandParams request) { - return JsonSerializer.Deserialize((JsonElement)request.Arguments.First(), ProtocolConversions.LspJsonSerializerOptions)!; + return JsonSerializer.Deserialize((JsonElement)request.Arguments!.First(), ProtocolConversions.LspJsonSerializerOptions)!; } public override Task HandleRequestAsync(ExecuteCommandParams request, RequestContext context, CancellationToken cancellationToken) diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 12c2b718c5e91..04875a010bc6f 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -156,7 +156,7 @@ public void VerifyLspClientOptionNames() string.Join(Environment.NewLine, actualNames)); } - private static void VerifyValuesInServer(EditorTestWorkspace workspace, List expectedValues) + private static void VerifyValuesInServer(LspTestWorkspace workspace, List expectedValues) { var globalOptionService = workspace.GetService(); var supportedOptions = DidChangeConfigurationNotificationHandler.SupportedOptions; @@ -244,7 +244,7 @@ private static string ConvertToString(object? value) => value switch { null => "null", - _ => value.ToString() + _ => value.ToString()! }; private static string GenerateNonDefaultValue(IOption2 option) diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 094dec54538ad..d13000fa95ea2 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -279,7 +279,8 @@ private protected static async Task> RunGet if (useProgress) { Assert.Null(diagnostics); - diagnostics = progress!.Value.GetValues().Single().First; + AssertEx.NotNull(progress); + diagnostics = progress.Value.GetValues()!.Single().First; } if (diagnostics == null) diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs index 140598171eaa0..1fcbd5de786d4 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs @@ -40,7 +40,7 @@ public async Task TestWorkspaceDiagnosticsReportsAdditionalFileDiagnostic(bool u @"C:\C.cs: []", @$"C:\Test.txt: [{MockAdditionalFileDiagnosticAnalyzer.Id}]", @"C:\CSProj1.csproj: []" - ], results.Select(r => $"{r.Uri.LocalPath}: [{string.Join(", ", r.Diagnostics.Select(d => d.Code?.Value?.ToString()))}]")); + ], results.Select(r => $"{r.Uri.LocalPath}: [{string.Join(", ", r.Diagnostics!.Select(d => d.Code?.Value?.ToString()))}]")); // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -64,7 +64,7 @@ public async Task TestWorkspaceDiagnosticsWithRemovedAdditionalFile(bool useVSDi Assert.Equal(3, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); AssertEx.Empty(results[2].Diagnostics); @@ -100,12 +100,12 @@ public async Task TestWorkspaceDiagnosticsWithAdditionalFileInMultipleProjects(b var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true); Assert.Equal(6, results.Length); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); - Assert.Equal("CSProj1", ((LSP.VSDiagnostic)results[1].Diagnostics.Single()).Projects.First().ProjectName); - Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[4].Diagnostics.Single().Code); + Assert.Equal("CSProj1", ((LSP.VSDiagnostic)results[1].Diagnostics!.Single()).Projects!.First().ProjectName); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[4].Diagnostics!.Single().Code); Assert.Equal(@"C:\Test.txt", results[4].Uri.LocalPath); - Assert.Equal("CSProj2", ((LSP.VSDiagnostic)results[4].Diagnostics.Single()).Projects.First().ProjectName); + Assert.Equal("CSProj2", ((LSP.VSDiagnostic)results[4].Diagnostics!.Single()).Projects!.First().ProjectName); // Asking again should give us back an unchanged diagnostic. var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs index 0fc045014346d..4d83a1ccaa0a9 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticsPullCacheTests.cs @@ -35,7 +35,7 @@ public async Task TestDocumentDiagnosticsCallsDiagnosticSourceWhenVersionChanges await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make a change that modifies the versions we use to cache. @@ -65,7 +65,7 @@ public async Task TestDocumentDiagnosticsCallsDiagnosticSourceWhenGlobalVersionC await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make a global version change @@ -96,7 +96,7 @@ public async Task TestDocumentDiagnosticsDoesNotCallDiagnosticSourceWhenVersionS await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(TestDiagnosticSource.Id, results[0].Diagnostics!.Single().Code); Assert.Equal(1, testProvider.DiagnosticsRequestedCount); // Make another request without modifying anything and assert we did not re-calculate anything. diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs index c1379550ec811..a728c2f2a568e 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/NonLocalDiagnosticTests.cs @@ -45,7 +45,7 @@ internal async Task TestNonLocalDocumentDiagnosticsAreReportedWhenFSAEnabled(boo { Assert.Equal(1, results.Length); Assert.Equal(2, results[0].Diagnostics?.Length); - var orderedDiagnostics = results[0].Diagnostics.OrderBy(d => d.Code!.Value.Value).ToList(); + var orderedDiagnostics = results[0].Diagnostics!.OrderBy(d => d.Code!.Value.Value).ToList(); Assert.Equal(NonLocalDiagnosticsAnalyzer.NonLocalDescriptor.Id, orderedDiagnostics[0].Code); Assert.Equal(NonLocalDiagnosticsAnalyzer.CompilationEndDescriptor.Id, orderedDiagnostics[1].Code); Assert.Equal(document.GetURI(), results[0].Uri); diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index 07731ea745e5e..7b9becddaa4dd 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -56,9 +56,6 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -66,8 +63,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] @@ -79,9 +76,6 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, additionalAnalyzers: additionalAnalyzers); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -92,8 +86,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool var semanticResults = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSemantic); - Assert.Equal("CS1513", syntaxResults.Single().Diagnostics.Single().Code); - Assert.Equal("CS0246", semanticResults.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", syntaxResults.Single().Diagnostics!.Single().Code); + Assert.Equal("CS0246", semanticResults.Single().Diagnostics!.Single().Code); var syntaxResults2 = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); @@ -109,8 +103,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); - Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics.Single().Code); - Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics.Single().Code); + Assert.Equal(CSharpSyntaxAnalyzer.RuleId, syntaxAnalyzerResults.Single().Diagnostics!.Single().Code); + Assert.Equal(CSharpSemanticAnalyzer.RuleId, semanticAnalyzerResults.Single().Diagnostics!.Single().Code); var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); @@ -164,17 +158,14 @@ static void Main(string[] args) """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("IDE0060", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("IDE0060", results.Single().Diagnostics!.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics!.Single(); Assert.Equal(vsDiagnostic.ExpandedMessage, AnalyzersResources.Avoid_unused_parameters_in_your_code_If_the_parameter_cannot_be_removed_then_change_its_name_so_it_starts_with_an_underscore_and_is_optionally_followed_by_an_integer_such_as__comma__1_comma__2_etc_These_are_treated_as_special_discard_symbol_names); } @@ -184,9 +175,6 @@ public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatin var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -194,8 +182,8 @@ public async Task TestDocumentDiagnosticsUsesNullForExpandedMessage(bool mutatin var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics.Single(); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + var vsDiagnostic = (VSDiagnostic)results.Single().Diagnostics!.Single(); Assert.Null(vsDiagnostic.ExpandedMessage); } @@ -210,9 +198,6 @@ class A { """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -220,8 +205,8 @@ class A { var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.Task); - Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); - Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); + Assert.Equal("TODO", results.Single().Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results.Single().Diagnostics!.Single().Message); } [Theory, CombinatorialData] @@ -231,13 +216,11 @@ public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(b await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData] @@ -247,9 +230,6 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var workspace = testLspServer.TestWorkspace; - // Calling GetTextBuffer will effectively open the file. - workspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); // Get the diagnostics for the solution containing the doc. @@ -259,7 +239,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics).ConfigureAwait(false); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); // Now remove the doc. workspace.OnDocumentRemoved(workspace.Documents.Single().Id); @@ -278,16 +258,13 @@ public async Task TestNoChangeIfDocumentDiagnosticsCalledTwice(bool useVSDiagnos var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); var resultId = results.Single().ResultId; results = await RunGetDocumentPullDiagnosticsAsync( @@ -304,16 +281,13 @@ public async Task TestDocumentDiagnosticsWhenGlobalStateChanges(bool useVSDiagno var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); var resultId = results.Single().ResultId; @@ -334,17 +308,15 @@ public async Task TestDocumentDiagnosticsRemovedAfterErrorIsFixed(bool useVSDiag var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var text = await document.GetTextAsync(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); - await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "}"); + await InsertTextAsync(testLspServer, document, text.Length, "}"); results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId); AssertEx.Empty(results[0].Diagnostics); @@ -356,25 +328,22 @@ public async Task TestDocumentDiagnosticsRemainAfterErrorIsNotFixed(bool useVSDi var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + var text = await document.GetTextAsync(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); - buffer.Insert(0, " "); await InsertTextAsync(testLspServer, document, position: 0, text: " "); results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: results[0].ResultId); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results[0].Diagnostics!.Single().Range.Start); } [Theory, CombinatorialData] @@ -387,9 +356,6 @@ class A { """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -397,8 +363,8 @@ class A { var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.Equal(1, results.Single().Diagnostics.Single().Range.Start.Line); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.Equal(1, results.Single().Diagnostics!.Single().Range.Start.Line); } [Theory, CombinatorialData] @@ -407,16 +373,13 @@ public async Task TestStreamingDocumentDiagnostics(bool useVSDiagnostics, bool m var markup = @"class A {"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, useProgress: true); - Assert.Equal("CS1513", results!.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results!.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData] @@ -449,18 +412,21 @@ class B { // Open either of the documents via LSP, we're tracking the URI and text. await OpenDocumentAsync(testLspServer, csproj1Document); - // This opens all documents in the workspace and ensures buffers are created. - testLspServer.TestWorkspace.GetTestDocument(csproj1Document.Id)!.GetTextBuffer(); + // If we don't have a mutating workspace, we need to manually open all linked documents in the workspace (otherwise updating the context will not succeed). + if (!mutatingLspWorkspace) + { + await testLspServer.OpenDocumentInWorkspaceAsync(csproj2Document.Id, openAllLinkedDocuments: true); + } // Set CSProj2 as the active context and get diagnostics. testLspServer.TestWorkspace.SetDocumentContext(csproj2Document.Id); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj2Document.GetURI(), useVSDiagnostics); - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); if (useVSDiagnostics) { // Only VSDiagnostics will have the project. - var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics.Single(); - Assert.Equal("CSProj2", vsDiagnostic.Projects.Single().ProjectName); + var vsDiagnostic = (LSP.VSDiagnostic)results.Single().Diagnostics!.Single(); + Assert.Equal("CSProj2", vsDiagnostic.Projects!.Single().ProjectName); } // Set CSProj1 as the active context and get diagnostics. @@ -471,7 +437,7 @@ class B { if (useVSDiagnostics) { - AssertEx.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects.Single().ProjectName)); + AssertEx.All(results.Single().Diagnostics, d => Assert.Equal("CSProj1", ((VSDiagnostic)d).Projects!.Single().ProjectName)); } } @@ -500,9 +466,9 @@ public async Task TestDocumentDiagnosticsHasSameIdentifierForLinkedFile(bool mut await OpenDocumentAsync(testLspServer, csproj1Document); var csproj1Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj1Document), useVSDiagnostics: true); - var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics.Single(); + var csproj1Diagnostic = (VSDiagnostic)csproj1Results.Single().Diagnostics!.Single(); var csproj2Results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, GetVsTextDocumentIdentifier(csproj2Document), useVSDiagnostics: true); - var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics.Single(); + var csproj2Diagnostic = (VSDiagnostic)csproj2Results.Single().Diagnostics!.Single(); Assert.Equal(csproj1Diagnostic.Identifier, csproj2Diagnostic.Identifier); static VSTextDocumentIdentifier GetVsTextDocumentIdentifier(Document document) @@ -561,7 +527,7 @@ public class {|caret:|} { } // Verify we a diagnostic in A.cs since B does not exist. var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS0246", results.Single().Diagnostics!.Single().Code); // Insert B into B.cs and verify that the error in A.cs is now gone. var locationToReplace = testLspServer.GetLocations("caret").Single().Range; @@ -613,7 +579,7 @@ public class {|caret:|} { } // Verify we get a diagnostic in A since the class B does not exist. var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, csproj1Document.GetURI(), useVSDiagnostics); Assert.Single(results); - Assert.Equal("CS0246", results.Single().Diagnostics.Single().Code); + Assert.Equal("CS0246", results.Single().Diagnostics!.Single().Code); // Add B to CSProj2 and verify that we get an unchanged result (still has diagnostic) for A.cs // since CSProj1 does not reference CSProj2 @@ -634,9 +600,6 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.RazorLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -645,8 +608,8 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics, testLspServer, document.GetURI(), useVSDiagnostics); // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); } [Theory, CombinatorialData] @@ -657,9 +620,6 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, CompilerDiagnosticsScope.OpenFiles, useVSDiagnostics, WellKnownLspServerKinds.LiveShareLspServer)); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -668,8 +628,8 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti testLspServer, document.GetURI(), useVSDiagnostics); // Assert that we have diagnostics even though the option is set to push. - Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); - Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); + Assert.Equal("CS1513", results.Single().Diagnostics!.Single().Code); + Assert.NotNull(results.Single().Diagnostics!.Single().CodeDescription!.Href); } [Theory, CombinatorialData] @@ -679,9 +639,6 @@ public async Task TestDocumentDiagnosticsIncludesSourceGeneratorDiagnostics(bool await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); var generator = new DiagnosticProducingGenerator(context => Location.Create(context.Compilation.SyntaxTrees.Single(), new TextSpan(0, 10))); @@ -807,14 +764,14 @@ void M() else { // There should be one unnecessary diagnostic. - Assert.True(results.Single().Diagnostics.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); - Assert.Equal(lineLocation, results.Single().Diagnostics.Single().Range); + Assert.True(results.Single().Diagnostics!.Single().Tags!.Contains(DiagnosticTag.Unnecessary)); + Assert.Equal(lineLocation, results.Single().Diagnostics!.Single().Range); // There should be an additional location for the open paren. - Assert.Equal(openLocation, results.Single().Diagnostics.Single().RelatedInformation![0].Location.Range); + Assert.Equal(openLocation, results.Single().Diagnostics!.Single().RelatedInformation![0].Location.Range); // There should be an additional location for the close paren. - Assert.Equal(closeLocation, results.Single().Diagnostics.Single().RelatedInformation![1].Location.Range); + Assert.Equal(closeLocation, results.Single().Diagnostics!.Single().RelatedInformation![1].Location.Range); } } @@ -831,7 +788,7 @@ public async Task TestDocumentDiagnosticsForUnnecessarySuppressions(bool useVSDi var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics); - Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics.Single().Code); + Assert.Equal(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, results.Single().Diagnostics!.Single().Code); } [Theory, CombinatorialData, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1824321")] @@ -872,9 +829,6 @@ class A """; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics: true); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -882,8 +836,8 @@ class A var results = await RunGetDocumentPullDiagnosticsAsync( testLspServer, document.GetURI(), useVSDiagnostics: true); - Assert.Equal("IDE0090", results.Single().Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics.Single().Severity); + Assert.Equal("IDE0090", results.Single().Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Information, results.Single().Diagnostics!.Single().Severity); } #endregion @@ -914,7 +868,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiag var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); } @@ -933,16 +887,15 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisAndFS var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); // this should be considered a build-error, since it was produced by the last code-analysis run. - Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags!); + Assert.Contains(VSDiagnosticTags.BuildError, results[0].Diagnostics!.Single().Tags!); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); // Now fix the compiler error, but don't re-execute code analysis. // Verify that we still get the workspace diagnostics from the prior snapshot on which code analysis was executed. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -974,16 +927,15 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithRunCodeAnalysisFSAOn var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); // this should *not* be considered a build-error, since it was produced by the live workspace results. - Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics.Single().Tags!); + Assert.DoesNotContain(VSDiagnosticTags.BuildError, results[0].Diagnostics!.Single().Tags!); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); // Now fix the compiler error, but don't rerun code analysis. // Verify that we get up-to-date workspace diagnostics, i.e. no compiler errors, from the current snapshot because FSA is enabled. - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -1021,7 +973,7 @@ public async Task SourceGeneratorFailures_FSA(bool useVSDiagnostics, bool mutati Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.True(results[1].Diagnostics.Single().Message.Contains("Source generator failed")); + Assert.True(results[1].Diagnostics!.Single().Message.Contains("Source generator failed")); } [Theory, CombinatorialData] @@ -1056,9 +1008,9 @@ class A { var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); + Assert.Equal(VSDiagnosticRank.Default, ((VSDiagnostic)results[0].Diagnostics!.Single()).DiagnosticRank); } [Theory] @@ -1088,9 +1040,9 @@ class A { var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics.Single()).DiagnosticRank); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); + Assert.Equal(rank, ((VSDiagnostic)results[0].Diagnostics!.Single()).DiagnosticRank); } [Theory, CombinatorialData] @@ -1126,8 +1078,8 @@ class A { Assert.Equal(1, results.Length); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Equal("TODO", results[0].Diagnostics!.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics!.Single().Message); } [Theory, CombinatorialData] @@ -1270,23 +1222,27 @@ public async Task EditAndContinue(bool useVSDiagnostics, bool mutatingLspWorkspa AssertEx.Equal([], workspaceResults3.Select(Inspect)); static DiagnosticData CreateDiagnostic(string id, Document? document = null, Project? project = null) - => new( - id, - category: "EditAndContinue", - message: "test message", - severity: DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true, - warningLevel: 0, - projectId: project?.Id, - customTags: [], - properties: ImmutableDictionary.Empty, - location: new DiagnosticDataLocation(new FileLinePositionSpan("file", span: default), document?.Id), - additionalLocations: [], - language: (project ?? document!.Project).Language); + { + return new( + id, + category: "EditAndContinue", + message: "test message", + severity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + warningLevel: 0, + projectId: project?.Id, + customTags: [], + properties: ImmutableDictionary.Empty, + location: new DiagnosticDataLocation(new FileLinePositionSpan("file", span: default), document?.Id), + additionalLocations: [], + language: (project ?? document!.Project).Language); + } static string Inspect(TestDiagnosticResult result) - => $"{result.TextDocument.Uri} -> [{string.Join(",", result.Diagnostics?.Select(d => d.Code?.Value) ?? [])}]"; + { + return $"{result.TextDocument.Uri} -> [{string.Join(",", result.Diagnostics?.Select(d => d.Code?.Value) ?? [])}]"; + } } [Theory, CombinatorialData] @@ -1322,7 +1278,7 @@ public async Task TestWorkspaceDiagnosticsIncludesSourceGeneratorDiagnosticsClos var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics.Single().Code); + Assert.Equal(DiagnosticProducingGenerator.Descriptor.Id, results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); } @@ -1390,7 +1346,7 @@ public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiag Assert.Equal(3, results.Length); // Since we sorted above by URI the first result is the project. AssertEx.Empty(results[0].Diagnostics); - Assert.Equal("CS1513", results[1].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[1].Diagnostics!.Single().Code); AssertEx.Empty(results[2].Diagnostics); } @@ -1405,7 +1361,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); @@ -1431,7 +1387,7 @@ public async Task TestNoChangeIfWorkspaceDiagnosticsCalledTwice(bool useVSDiagno var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); @@ -1452,12 +1408,11 @@ public async Task TestWorkspaceDiagnosticsRemovedAfterErrorIsFixed(bool useVSDia var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "}"); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, "}"); var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); @@ -1478,27 +1433,21 @@ public async Task TestWorkspaceDiagnosticsRemainAfterErrorIsNotFixed(bool useVSD var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(0, " "); + await InsertInClosedDocumentAsync(testLspServer, testLspServer.TestWorkspace.Documents.First().Id, " ", position: 0); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.First(); var text = await document.GetTextAsync(); - // Hacky, but we need to close the document manually since editing the text-buffer will open it in the - // test-workspace. - testLspServer.TestWorkspace.OnDocumentClosed( - document.Id, TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()))); - var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); - Assert.Equal("CS1513", results2[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results2[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 10 }, results2[0].Diagnostics!.Single().Range.Start); AssertEx.Empty(results2[1].Diagnostics); Assert.NotEqual(results[1].ResultId, results2[1].ResultId); @@ -1517,8 +1466,8 @@ public async Task TestStreamingWorkspaceDiagnostics(bool useVSDiagnostics, bool var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics.Single().Range.Start); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(new Position { Line = 0, Character = 9 }, results[0].Diagnostics!.Single().Range.Start); results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, useProgress: true); @@ -1540,8 +1489,8 @@ class A { var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(3, results.Length); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(@"C:\test1.cs"), results[0].TextDocument!.Uri); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); - Assert.Equal(1, results[0].Diagnostics.Single().Range.Start.Line); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); + Assert.Equal(1, results[0].Diagnostics!.Single().Range.Start.Line); AssertEx.Empty(results[1].Diagnostics); AssertEx.Empty(results[2].Diagnostics); } @@ -1585,8 +1534,8 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); // Insert B into B.cs via the workspace. var caretLocation = testLspServer.GetLocations("caret").First().Range; @@ -1667,13 +1616,13 @@ public class {|caret:|} AssertEx.NotNull(results); Assert.Equal(6, results.Length); // Type C does not exist. - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); // Type C does not exist. - Assert.Equal("CS0246", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[2].Diagnostics!.Single().Code); AssertEx.Empty(results[3].Diagnostics); // Syntax error missing identifier. - Assert.Equal("CS1001", results[4].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[4].Diagnostics!.Single().Code); AssertEx.Empty(results[5].Diagnostics); // Insert C into C.cs via the workspace. @@ -1691,7 +1640,7 @@ public class {|caret:|} Assert.Equal(3, results.Length); // A.cs should report CS0012 indicating that C is not directly referenced. - Assert.Equal("CS0012", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0012", results[0].Diagnostics!.Single().Code); Assert.NotEqual(previousResultIds[0].resultId, results[0].ResultId); // B.cs should no longer have a diagnostic since C exists. @@ -1741,9 +1690,9 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); AssertEx.Empty(results[3].Diagnostics); // Insert B into B.cs via the workspace. @@ -1810,8 +1759,8 @@ public static void Do() AssertEx.NotNull(results); Assert.Equal(4, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal("CS0168", results[2].Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Warning, results[2].Diagnostics.Single().Severity); + Assert.Equal("CS0168", results[2].Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Warning, results[2].Diagnostics!.Single().Severity); // Change and reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -1829,8 +1778,8 @@ public static void Do() // We should get a single report back for B.cs now that the diagnostic has been promoted to an error. // The diagnostics in A.cs did not change and so are not reported again. Assert.Equal(1, results.Length); - Assert.Equal("CS0168", results[0].Diagnostics.Single().Code); - Assert.Equal(LSP.DiagnosticSeverity.Error, results[0].Diagnostics.Single().Severity); + Assert.Equal("CS0168", results[0].Diagnostics!.Single().Code); + Assert.Equal(LSP.DiagnosticSeverity.Error, results[0].Diagnostics!.Single().Severity); } [Theory, CombinatorialData] @@ -1872,8 +1821,8 @@ public class {|caret:|} { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(4, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); - Assert.Equal("CS1001", results[2].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); + Assert.Equal("CS1001", results[2].Diagnostics!.Single().Code); // Reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -1928,7 +1877,7 @@ class A : B { } var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); AssertEx.NotNull(results); Assert.Equal(6, results.Length); - Assert.Equal("CS0246", results[0].Diagnostics.Single().Code); + Assert.Equal("CS0246", results[0].Diagnostics!.Single().Code); // Reload the project via the workspace. var projectInfo = testLspServer.TestWorkspace.Projects.Where(p => p.AssemblyName == "CSProj2").Single().ToProjectInfo(); @@ -2098,7 +2047,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOnToOff(boo var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); Assert.Equal(2, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); var options = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); @@ -2143,9 +2092,17 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesSwitchFSAFromOffToOn(boo results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); Assert.Equal(2, results.Length); - Assert.Equal("CS1513", results[0].Diagnostics.Single().Code); + Assert.Equal("CS1513", results[0].Diagnostics!.Single().Code); AssertEx.Empty(results[1].Diagnostics); } + internal static async Task InsertInClosedDocumentAsync(TestLspServer testLspServer, DocumentId documentId, string textToInsert, int? position = null) + { + var text = await testLspServer.GetCurrentSolution().GetDocument(documentId)!.GetTextAsync(CancellationToken.None); + position ??= text.Length; + text = text.WithChanges(new TextChange(new TextSpan(position.Value, 0), textToInsert)); + await testLspServer.TestWorkspace.ChangeDocumentAsync(documentId, text); + } + #endregion } diff --git a/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs b/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs index ce607c6a9fc25..93cf0d63cc83f 100644 --- a/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs @@ -29,7 +29,7 @@ public async Task TestWorkspaceDiagnosticsReportsProjectDiagnostic(bool useVSDia Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(testLspServer.GetCurrentSolution().Projects.First().FilePath!), results[1].Uri); // Asking again should give us back an unchanged diagnostic. @@ -46,7 +46,7 @@ public async Task TestWorkspaceDiagnosticsWithRemovedProject(bool useVSDiagnosti Assert.Equal(2, results.Length); AssertEx.Empty(results[0].Diagnostics); - Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(MockProjectDiagnosticAnalyzer.Id, results[1].Diagnostics!.Single().Code); Assert.Equal(ProtocolConversions.CreateAbsoluteUri(testLspServer.GetCurrentSolution().Projects.First().FilePath!), results[1].Uri); var initialSolution = testLspServer.GetCurrentSolution(); diff --git a/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs b/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs index 5968d8f97ca07..5963b91d09b83 100644 --- a/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/InlayHint/CSharpInlayHintTests.cs @@ -130,8 +130,9 @@ void M() }; var actualInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + AssertEx.NotNull(actualInlayHints); var firstInlayHint = actualInlayHints.First(); - var data = JsonSerializer.Deserialize(firstInlayHint.Data!.ToString(), ProtocolConversions.LspJsonSerializerOptions); + var data = JsonSerializer.Deserialize(firstInlayHint.Data!.ToString()!, ProtocolConversions.LspJsonSerializerOptions); AssertEx.NotNull(data); var firstResultId = data.ResultId; @@ -143,6 +144,7 @@ void M() await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); var lastInlayHints = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentInlayHintName, inlayHintParams, CancellationToken.None); + AssertEx.NotNull(lastInlayHints); Assert.True(lastInlayHints.Any()); // Assert that the first result id is no longer in the cache. diff --git a/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs b/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs index 0aaf597dec428..2947fd4a7dcf1 100644 --- a/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/MapCode/MapCodeTests.cs @@ -112,7 +112,7 @@ static void Main(string[] args) var results = await testLspServer.ExecuteRequestAsync(VSInternalMethods.WorkspaceMapCodeName, mapCodeParams, CancellationToken.None); AssertEx.NotNull(results); - TextEdit[] edits; + TextEdit[]? edits; if (supportDocumentChanges) { Assert.Null(results.Changes); @@ -131,6 +131,8 @@ static void Main(string[] args) Assert.True(results.Changes!.TryGetValue(ProtocolConversions.GetDocumentFilePathFromUri(documentUri), out edits)); } + AssertEx.NotNull(edits); + var documentText = await document.GetTextAsync(); var actualText = ApplyTextEdits(edits, documentText); Assert.Equal(expected, actualText); diff --git a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs index 36e37923ab780..733d6c9306bd8 100644 --- a/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Metadata/LspMetadataAsSourceWorkspaceTests.cs @@ -43,6 +43,7 @@ void M() var location = testLspServer.GetLocations("definition").Single(); var definition = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName, CreateTextDocumentPositionParams(location), CancellationToken.None); + AssertEx.NotNull(definition); // Open the metadata file and verify it gets added to the metadata workspace. await testLspServer.OpenDocumentAsync(definition.Single().Uri, text: string.Empty).ConfigureAwait(false); @@ -89,6 +90,7 @@ public static void WriteLine(string value) {} var location = testLspServer.GetLocations("definition").Single(); var definition = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentDefinitionName, CreateTextDocumentPositionParams(location), CancellationToken.None); + AssertEx.NotNull(definition); // Open the metadata file and verify it gets added to the metadata workspace. // We don't have the real metadata source, so just populate it with our fake metadata source. diff --git a/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj b/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj index 1cf6c995899f4..77dcec96afa1b 100644 --- a/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj +++ b/src/LanguageServer/ProtocolUnitTests/Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests.csproj @@ -3,7 +3,7 @@ - net472 + $(NetVSCode);net472 Library Microsoft.CodeAnalysis.LanguageServer.UnitTests UnitTest @@ -23,13 +23,6 @@ - - - - - - - @@ -43,11 +36,9 @@ + - - - diff --git a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index be2a76a84c976..5299ad3bec399 100644 --- a/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -286,6 +286,7 @@ void M() Assert.Equal(LanguageNames.CSharp, miscDoc.Project.Language); // Verify GTD request succeeded. + AssertEx.NotNull(result); Assert.Equal(0, result.Single().Range.Start.Line); Assert.Equal(6, result.Single().Range.Start.Character); Assert.Equal(0, result.Single().Range.End.Line); diff --git a/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs b/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs index f85284982a6ef..22f7e85fe4175 100644 --- a/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Options/LspOptionsTests.cs @@ -6,8 +6,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; diff --git a/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs b/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs index 44befe94053bb..a255360e48280 100644 --- a/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/ProjectContext/GetTextDocumentWithContextHandlerTests.cs @@ -81,11 +81,9 @@ public async Task SwitchingContextsChangesDefaultContext(bool mutatingLspWorkspa await using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, mutatingLspWorkspace); - // Ensure the documents are open so we can change contexts - foreach (var document in testLspServer.TestWorkspace.Documents) - { - _ = document.GetOpenTextContainer(); - } + // Ensure all the linked documents are open so we can change contexts + var document = testLspServer.TestWorkspace.Documents.First(); + await testLspServer.OpenDocumentInWorkspaceAsync(document.Id, openAllLinkedDocuments: true); var documentUri = testLspServer.GetLocations("caret").Single().Uri; diff --git a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs index 8fda99724efe7..d57d43540f495 100644 --- a/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerFeaturesTests.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -70,8 +69,8 @@ class SomeClass{{i}} } """; - var testDocument = new EditorTestHostDocument(text: source, displayName: @$"C:\SomeFile{i}.cs", exportProvider: testLspServer.TestWorkspace.ExportProvider, filePath: @$"C:\SomeFile{i}.cs"); - testLspServer.TestWorkspace.AddTestProject(new EditorTestHostProject(testLspServer.TestWorkspace, documents: [testDocument])); + var testDocument = new TestHostDocument(text: source, displayName: @$"C:\SomeFile{i}.cs", exportProvider: testLspServer.TestWorkspace.ExportProvider, filePath: @$"C:\SomeFile{i}.cs"); + testLspServer.TestWorkspace.AddTestProject(new TestHostProject(testLspServer.TestWorkspace, documents: [testDocument])); } await WaitForWorkspaceOperationsAsync(testLspServer.TestWorkspace); diff --git a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs index 979a37c00517c..d4965c90a9321 100644 --- a/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/RelatedDocuments/RelatedDocumentsTests.cs @@ -95,7 +95,7 @@ class Y useProgress: useProgress); Assert.Equal(1, results.Length); - Assert.Equal(project.Documents.Last().FilePath, results[0].FilePaths.Single()); + Assert.Equal(project.Documents.Last().FilePath, results[0].FilePaths!.Single()); } [Theory, CombinatorialData] @@ -129,8 +129,8 @@ class Z project.Documents.First().GetURI(), useProgress: useProgress); - Assert.Equal(2, results.SelectMany(r => r.FilePaths).Count()); - AssertEx.SetEqual([.. project.Documents.Skip(1).Select(d => d.FilePath)], results.SelectMany(r => r.FilePaths)); + Assert.Equal(2, results.SelectMany(r => r.FilePaths!).Count()); + AssertEx.SetEqual([.. project.Documents.Skip(1).Select(d => d.FilePath)], results.SelectMany(r => r.FilePaths!)); } [Theory, CombinatorialData] diff --git a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs index 68c71fadb2528..870cbd83c119b 100644 --- a/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Rename/RenameTests.cs @@ -22,7 +22,7 @@ public RenameTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRenameAsync(bool mutatingLspWorkspace) { var markup = @@ -45,7 +45,7 @@ void M2() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_InvalidIdentifierAsync(bool mutatingLspWorkspace) { var markup = @@ -67,7 +67,7 @@ void M2() Assert.Null(results); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithLinkedFilesAsync(bool mutatingLspWorkspace) { var markup = @@ -101,7 +101,7 @@ void M2() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithLinkedFilesAndPreprocessorAsync(bool mutatingLspWorkspace) { var markup = @@ -147,7 +147,7 @@ void M4() AssertJsonEquals(expectedEdits, ((TextDocumentEdit[])results.DocumentChanges).First().Edits); } - [WpfTheory, CombinatorialData] + [Theory, CombinatorialData] public async Task TestRename_WithMappedFileAsync(bool mutatingLspWorkspace) { var markup = diff --git a/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs b/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs index f9eaf873a3a5f..aad9886aeadb5 100644 --- a/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; @@ -48,9 +49,7 @@ public async Task TestDocumentResultsForOpenFiles(bool mutatingLspWorkspace) }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. var testDocument = testLspServer.TestWorkspace.Documents.Single(); - testDocument.GetTextBuffer(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -81,7 +80,6 @@ class {|Identifier:A{{v}}|} """)); await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. var testDocument = testLspServer.TestWorkspace.Documents.Single(); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -114,9 +112,6 @@ public async Task TestDocumentResultsForRemovedDocument(bool mutatingLspWorkspac await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var workspace = testLspServer.TestWorkspace; - // Calling GetTextBuffer will effectively open the file. - workspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); // Get the diagnostics for the solution containing the doc. @@ -153,9 +148,6 @@ public async Task TestNoChangeIfDocumentResultsCalledTwice(bool mutatingLspWorks }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -189,9 +181,6 @@ public async Task TestDocumentResultChangedAfterEntityAdded(bool mutatingLspWork "; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -206,7 +195,7 @@ public async Task TestDocumentResultChangedAfterEntityAdded(bool mutatingLspWork Ranges = GetRanges(testLspServer.TestWorkspace.Documents.Single().AnnotatedSpans), }); - await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "// comment"); + await InsertTextAsync(testLspServer, document, sourceText.Length, "// comment"); var (_, lspSolution) = await testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None).ConfigureAwait(false); document = lspSolution!.Projects.Single().Documents.Single(); @@ -237,9 +226,6 @@ public async Task TestDocumentResultIdSameAfterIrrelevantEdit(bool mutatingLspWo }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - var buffer = testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -277,9 +263,6 @@ class {|Identifier:A|} }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -304,9 +287,6 @@ public async Task TestStreamingDocumentDiagnostics(bool mutatingLspWorkspace) }"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); - // Calling GetTextBuffer will effectively open the file. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); - var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); @@ -497,8 +477,7 @@ public async Task TestWorkspaceResultUpdatedAfterEdit(bool mutatingLspWorkspace) }); AssertEx.Empty(results[1].Ranges); - var buffer = testLspServer.TestWorkspace.Documents.First().GetTextBuffer(); - buffer.Insert(buffer.CurrentSnapshot.Length, "// comment"); + await PullDiagnosticTests.InsertInClosedDocumentAsync(testLspServer, document.Id, "// comment"); var results2 = await RunGetWorkspaceSpellCheckSpansAsync(testLspServer, previousResults: CreateParamsFromPreviousReports(results)); diff --git a/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs b/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs index 3a90b5d341ab6..213721f2f4ea1 100644 --- a/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Symbols/WorkspaceSymbolsTests.cs @@ -21,7 +21,7 @@ public class WorkspaceSymbolsTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerProtocolTests(testOutputHelper) { private static void AssertSetEquals(LSP.SymbolInformation[] expected, LSP.SymbolInformation[]? results) - => Assert.True(expected.ToHashSet().SetEquals(results)); + => Assert.True(expected.ToHashSet().SetEquals(results!)); private Task CreateTestLspServerAsync(string markup, bool mutatingLspWorkspace) => CreateTestLspServerAsync(markup, mutatingLspWorkspace, composition: Composition.AddParts(typeof(TestWorkspaceNavigateToSearchHostService))); diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index 5866d332036e1..8226be4f8c8b4 100644 --- a/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -298,7 +298,7 @@ public async Task TestUsesRegisteredHostWorkspace(bool mutatingLspWorkspace) // Verify 1 workspace registered to start with. Assert.True(IsWorkspaceRegistered(testLspServer.TestWorkspace, testLspServer)); - using var testWorkspaceTwo = EditorTestWorkspace.Create( + using var testWorkspaceTwo = LspTestWorkspace.Create( XElement.Parse(secondWorkspaceXml), workspaceKind: "OtherWorkspaceKind", composition: testLspServer.TestWorkspace.Composition); @@ -512,14 +512,15 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync(bool mutatingLspWor } [Theory, CombinatorialData] - public async Task TestDoesNotForkWhenDocumentTextBufferOpenedAsync(bool mutatingLspWorkspace) + public async Task TestDoesNotForkWhenDocumentOpenedInWorkspaceAsync(bool mutatingLspWorkspace) { var markup = "Text"; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var documentUri = testLspServer.GetCurrentSolution().Projects.First().Documents.First().GetURI(); - // Calling get text buffer opens the document in the workspace. - testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + // Open document in the workspace. + await testLspServer.OpenDocumentInWorkspaceAsync(document.Id, openAllLinkedDocuments: false); await testLspServer.OpenDocumentAsync(documentUri, "Text"); diff --git a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs index 1d6ae9f07598d..88d6e061debe9 100644 --- a/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Workspaces/SourceGeneratedDocumentTests.cs @@ -281,7 +281,7 @@ public async Task TestReturnsNullForRemovedOpenedGeneratedFile(bool mutatingLspW Assert.Null(secondRequest.Text); } - private static async Task WaitForSourceGeneratorsAsync(EditorTestWorkspace workspace) + private static async Task WaitForSourceGeneratorsAsync(LspTestWorkspace workspace) { var operations = workspace.ExportProvider.GetExportedValue(); await operations.WaitAllAsync(workspace, [FeatureAttribute.Workspace, FeatureAttribute.SourceGenerators]); diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt index 7f8d403ef4ae5..ae3b53b6e80d9 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt @@ -542,6 +542,7 @@ Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(System.String) Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGeneratorsForAllLanguages Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetHashCode +Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.ToString Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.add_AnalyzerLoadFailed(System.EventHandler{Microsoft.CodeAnalysis.Diagnostics.AnalyzerLoadFailureEventArgs}) Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.get_AssemblyLoader Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.get_Display diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs index 5b549fcc253d8..9df6aa70e214d 100644 --- a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs +++ b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading; @@ -9,17 +10,17 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.UnitTests; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServices.DocumentOutline; using Microsoft.VisualStudio.Text; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Test.Utilities; using StreamJsonRpc; using Xunit.Abstractions; using static Roslyn.Test.Utilities.AbstractLanguageServerProtocolTests; @@ -94,7 +95,7 @@ protected async Task CreateMocksAsync(string code) } } - private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace) + private async Task CreateTestLspServerAsync(EditorTestWorkspace workspace) { var solution = workspace.CurrentSolution; @@ -118,10 +119,25 @@ private async Task CreateTestLspServerAsync(EditorTestWorkspace w solution = solution.WithAnalyzerReferences([new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap())]); await workspace.ChangeSolutionAsync(solution); - var server = await TestLspServer.CreateAsync(workspace, new InitializationOptions(), _logger); + var server = await EditorTestLspServer.CreateAsync(workspace, new InitializationOptions(), _logger); return server; } + internal class EditorTestLspServer : AbstractTestLspServer + { + private EditorTestLspServer(EditorTestWorkspace testWorkspace, Dictionary> locations, InitializationOptions options, AbstractLspLogger logger) : base(testWorkspace, locations, options, logger) + { + } + + public static async Task CreateAsync(EditorTestWorkspace testWorkspace, InitializationOptions initializationOptions, AbstractLspLogger logger) + { + var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); + var server = new EditorTestLspServer(testWorkspace, locations, initializationOptions, logger); + await server.InitializeAsync(); + return server; + } + } + [DataContract] private class NewtonsoftInitializeParams { diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index cab7be9c43233..3b17836fefa1e 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -16,6 +16,7 @@ + diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs index e6babc4236130..64ff1b7dde1fb 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractPackage.cs @@ -35,16 +35,17 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke Assumes.Present(_componentModel_doNotAccessDirectly); } - protected override Task OnAfterPackageLoadedAsync(CancellationToken cancellationToken) + protected override async Task OnAfterPackageLoadedAsync(CancellationToken cancellationToken) { + await base.OnAfterPackageLoadedAsync(cancellationToken).ConfigureAwait(false); + // TODO: remove, workaround for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1985204 var globalOptions = ComponentModel.GetService(); if (globalOptions.GetOption(SemanticSearchFeatureFlag.Enabled)) { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); UIContext.FromUIContextGuid(new Guid(SemanticSearchFeatureFlag.UIContextId)).IsActive = true; } - - return base.OnAfterPackageLoadedAsync(cancellationToken); } protected async Task LoadComponentsInUIContextOnceSolutionFullyLoadedAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/LiveShare/Test/AbstractLiveShareRequestHandlerTests.cs b/src/VisualStudio/LiveShare/Test/AbstractLiveShareRequestHandlerTests.cs index 7c54bc3a2e028..013e914955982 100644 --- a/src/VisualStudio/LiveShare/Test/AbstractLiveShareRequestHandlerTests.cs +++ b/src/VisualStudio/LiveShare/Test/AbstractLiveShareRequestHandlerTests.cs @@ -63,7 +63,7 @@ protected static async Task TestHandleAsync GetHandler(Solution solution, string methodName) { - var workspace = (EditorTestWorkspace)solution.Workspace; + var workspace = (LspTestWorkspace)solution.Workspace; var handlers = workspace.ExportProvider.GetExportedValues(LiveShareConstants.RoslynContractName); return (ILspRequestHandler)handlers.Single(handler => handler is ILspRequestHandler && IsMatchingMethod(handler, methodName)); diff --git a/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs b/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs index 3064aa9009685..6c97f2080b6e3 100644 --- a/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs +++ b/src/Workspaces/Core/Portable/CodeRefactorings/SyntaxEditorBasedCodeRefactoringProvider.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -28,9 +27,7 @@ internal abstract partial class SyntaxEditorBasedCodeRefactoringProvider : CodeR return FixAllProvider.Create( async (fixAllContext, document, fixAllSpans) => - { - return await this.FixAllAsync(document, fixAllSpans, fixAllContext.CodeActionEquivalenceKey, fixAllContext.CancellationToken).ConfigureAwait(false); - }, + await this.FixAllAsync(document, fixAllSpans, fixAllContext.CodeActionEquivalenceKey, fixAllContext.CancellationToken).ConfigureAwait(false), SupportedFixAllScopes); } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 7bb2618b19b0a..9c1adb44c8940 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.Serialization; +using static Microsoft.CodeAnalysis.Serialization.SerializerService.TestAccessor; using static TemporaryStorageService; internal partial class SerializerService @@ -29,18 +30,18 @@ internal partial class SerializerService /// pretend that a is a during tests. /// private static readonly object s_analyzerImageReferenceMapGate = new(); - private static IBidirectionalMap s_analyzerImageReferenceMap = BidirectionalMap.Empty; + private static IBidirectionalMap s_analyzerReferenceMap = BidirectionalMap.Empty; private static bool TryGetAnalyzerImageReferenceGuid(AnalyzerImageReference imageReference, out Guid guid) { lock (s_analyzerImageReferenceMapGate) - return s_analyzerImageReferenceMap.TryGetValue(imageReference, out guid); + return s_analyzerReferenceMap.TryGetValue(imageReference, out guid); } - private static bool TryGetAnalyzerImageReferenceFromGuid(Guid guid, [NotNullWhen(true)] out AnalyzerImageReference? imageReference) + private static bool TryGetAnalyzerImageReferenceFromGuid(Guid guid, [NotNullWhen(true)] out AnalyzerReference? analyzerReference) { lock (s_analyzerImageReferenceMapGate) - return s_analyzerImageReferenceMap.TryGetKey(guid, out imageReference); + return s_analyzerReferenceMap.TryGetKey(guid, out analyzerReference); } private static Checksum CreateChecksum(MetadataReference reference) @@ -78,6 +79,12 @@ protected virtual Checksum CreateChecksum(AnalyzerReference reference) writer.WriteGuid(guid); break; + case IAnalyzerReferenceWithGuid analyzerReferenceWithGuid: + lock (s_analyzerImageReferenceMapGate) + s_analyzerReferenceMap = s_analyzerReferenceMap.Add(reference, analyzerReferenceWithGuid.Guid); + writer.WriteGuid(analyzerReferenceWithGuid.Guid); + break; + default: throw ExceptionUtilities.UnexpectedValue(reference); } @@ -482,12 +489,6 @@ private static unsafe void WriteTo(MetadataReader reader, ObjectWriter writer) writer.WriteSpan(new ReadOnlySpan(reader.MetadataPointer, reader.MetadataLength)); } - private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference reference, ObjectWriter writer) - { - writer.WriteString(nameof(UnresolvedAnalyzerReference)); - writer.WriteString(reference.FullPath); - } - private static Metadata? TryGetMetadata(PortableExecutableReference reference) { try @@ -541,9 +542,23 @@ public static void AddAnalyzerImageReference(AnalyzerImageReference analyzerImag { lock (s_analyzerImageReferenceMapGate) { - if (!s_analyzerImageReferenceMap.ContainsKey(analyzerImageReference)) - s_analyzerImageReferenceMap = s_analyzerImageReferenceMap.Add(analyzerImageReference, Guid.NewGuid()); + if (!s_analyzerReferenceMap.ContainsKey(analyzerImageReference)) + s_analyzerReferenceMap = s_analyzerReferenceMap.Add(analyzerImageReference, Guid.NewGuid()); } } + + public static void AddAnalyzerImageReferences(IReadOnlyList analyzerReferences) + { + foreach (var analyzer in analyzerReferences) + { + if (analyzer is AnalyzerImageReference analyzerImageReference) + AddAnalyzerImageReference(analyzerImageReference); + } + } + + public interface IAnalyzerReferenceWithGuid + { + Guid Guid { get; } + } } } diff --git a/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs b/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs index c8a762042ae32..5371d55acc443 100644 --- a/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs +++ b/src/Workspaces/CoreTestUtilities/TestAnalyzerReferenceByLanguage.cs @@ -6,11 +6,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { - internal class TestAnalyzerReferenceByLanguage : AnalyzerReference + internal class TestAnalyzerReferenceByLanguage : + AnalyzerReference, + SerializerService.TestAccessor.IAnalyzerReferenceWithGuid { private readonly IReadOnlyDictionary> _analyzersMap; @@ -28,6 +31,7 @@ public TestAnalyzerReferenceByLanguage(IReadOnlyDictionary nameof(TestAnalyzerReferenceByLanguage); public override object Id => Display; + public Guid Guid { get; } = Guid.NewGuid(); public Checksum Checksum; diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs similarity index 95% rename from src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs rename to src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs index ec01c8c69affc..529f126e62ef1 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestDocumentTrackingService.cs @@ -7,7 +7,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.Editor.Test; +namespace Microsoft.CodeAnalysis.Test.Utilities; [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] [method: ImportingConstructor] diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index bac4fc5437153..40533be8db84b 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -774,15 +774,9 @@ public override bool TryApplyChanges(Solution newSolution) // Ensure that any in-memory analyzer references in this test workspace are known by the serializer service // so that we can validate OOP scenarios involving analyzers. - foreach (var analyzer in this.CurrentSolution.AnalyzerReferences) - { - if (analyzer is AnalyzerImageReference analyzerImageReference) - { #pragma warning disable CA1416 // Validate platform compatibility - SerializerService.TestAccessor.AddAnalyzerImageReference(analyzerImageReference); + SerializerService.TestAccessor.AddAnalyzerImageReferences(this.CurrentSolution.AnalyzerReferences); #pragma warning restore CA1416 // Validate platform compatibility - } - } return result; } From d8d312d82d088267c3f56e908197a841391349a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 14 Feb 2025 10:59:33 -0800 Subject: [PATCH 8/8] Defer checksum computation --- .../DiagnosticIncrementalAnalyzer_GetDiagnostics.cs | 8 ++++---- ...iagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 4df57f00f44d9..78746d3e3c66d 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -118,12 +118,12 @@ async Task> Ge // (since it runs all analyzers), we still run a paranoia check that the analyzers we care about are // a subset of that call so that we don't accidentally reuse results that would not correspond to // what we are computing ourselves. - var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); - if (_projectToForceAnalysisData.TryGetValue(project.State, out var box) && - box.Value.checksum == checksum && + if (s_projectToForceAnalysisData.TryGetValue(project.State, out var box) && analyzers.IsSubsetOf(box.Value.analyzers)) { - return box.Value.diagnosticAnalysisResults; + var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false); + if (box.Value.checksum == checksum) + return box.Value.diagnosticAnalysisResults; } // Otherwise, just compute for the analyzers we care about. diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index c472bbb22dbd5..889161e4e2647 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -32,7 +32,7 @@ private partial class DiagnosticIncrementalAnalyzer /// Practically, solution analyzers are the core Roslyn analyzers themselves we distribute, or analyzers shipped /// by vsix (not nuget). These analyzers do not get loaded after changing *until* VS restarts. /// - private static readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> _projectToForceAnalysisData = new(); + private static readonly ConditionalWeakTable analyzers, ImmutableDictionary diagnosticAnalysisResults)>> s_projectToForceAnalysisData = new(); public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { @@ -41,7 +41,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje try { - if (!_projectToForceAnalysisData.TryGetValue(projectState, out var box) || + if (!s_projectToForceAnalysisData.TryGetValue(projectState, out var box) || box.Value.checksum != checksum) { box = new(await ComputeForceAnalyzeProjectAsync().ConfigureAwait(false)); @@ -49,10 +49,10 @@ public async Task> ForceAnalyzeProjectAsync(Proje // Try to add the new computed data to the CWT. But use any existing value that another thread // might have beaten us to storing in it. #if NET - if (!_projectToForceAnalysisData.TryAdd(projectState, box)) - Contract.ThrowIfFalse(_projectToForceAnalysisData.TryGetValue(projectState, out box)); + if (!s_projectToForceAnalysisData.TryAdd(projectState, box)) + Contract.ThrowIfFalse(s_projectToForceAnalysisData.TryGetValue(projectState, out box)); #else - box = _projectToForceAnalysisData.GetValue(projectState, _ => box); + box = s_projectToForceAnalysisData.GetValue(projectState, _ => box); #endif }